package com.ship.dispatch.service.impl;

import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ship.common.core.constant.ShipApiConstants;
import com.ship.common.core.domain.SpPortData;
import com.ship.common.core.enums.MessageTypeEnum;
import com.ship.common.core.enums.ModuleUrlEnum;
import com.ship.common.core.exception.BaseException;
import com.ship.common.core.exception.ServiceException;
import com.ship.common.core.utils.DateUtils;
import com.ship.common.core.utils.OkHttpUtil;
import com.ship.common.core.web.domain.BaseResultCode;
import com.ship.common.core.web.domain.JsonResult;
import com.ship.common.business.service.impl.BaseServiceImpl;
import com.ship.common.core.utils.BeanCopyUtils;
import com.ship.common.security.utils.SecurityUtils;
import com.ship.dispatch.bean.*;
import com.ship.dispatch.bo.*;
import com.ship.dispatch.bpm.service.ProcessInstanceService;
import com.ship.dispatch.mapper.*;
import com.ship.dispatch.model.SpDispatchSchemeDetailModel;
import com.ship.dispatch.model.SpDispatchSchemeSaveModel;
import com.ship.dispatch.model.Vo.ShipLoadDaysAndRouteVo;
import com.ship.dispatch.model.Vo.ShipRouteSearchVo;
import com.ship.dispatch.model.dto.DispatchSchemeChartSaveDto;
import com.ship.dispatch.model.dto.SchemeChartSimpleDto;
import com.ship.dispatch.service.*;
import com.ship.dispatch.util.CommonUtil;
import com.ship.dispatch.util.IDUtil;
import com.ship.dispatch.vo.ConResourceSellerVO;
import com.ship.system.api.BpmInstanceService;
import com.ship.system.api.RemoteMessageSendService;
import com.ship.system.api.domain.SysMessageSend;
import com.ship.system.api.model.ProcessInstanceParam;
import lombok.extern.slf4j.Slf4j;
import okhttp3.MediaType;
import okhttp3.RequestBody;
import org.apache.commons.collections4.map.HashedMap;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

/**
 * <p>
 * 方案详情 服务实现类
 * </p>
 *
 * @author AbyssRabbit@163.com
 * @since 2023-10-17
 */
@Service
@Slf4j
public class SpDispatchSchemeDetailServiceImpl extends BaseServiceImpl<SpDispatchSchemeDetailMapper, SpDispatchSchemeDetail> implements SpDispatchSchemeDetailService {

    @Autowired(required = false)
    private SpYearPlanSchemeDetailMapper spYearPlanSchemeDetailMapper;

    @Autowired
    private SpYearPlanSchemeDetailService spYearPlanSchemeDetailService;

    @Autowired(required = false)
    private SpYearlyPlanMapper spYearlyPlanMapper;

    @Autowired(required = false)
    private SpYearPlanSchemeMapper planSchemeMapper;

    @Autowired
    @Lazy
    private SpInformationService spInformationService;

    @Autowired
    private SpShipScheduleService shipScheduleService;

    @Autowired
    private SpDispatchService spDispatchService;

    @Autowired(required = false)
    private ConShippingMapper conShippingMapper;

    @Autowired(required = false)
    private ConResourceMemorandumMapper resourceMemorandumMapper;

    @Autowired(required = false)
    private SpPortInformationMapper portInformationMapper;

    @Autowired
    @Lazy
    private SpArrangeSchemeService spArrangeSchemeService;

    private static String shipServerHost;
    private static String dispatchSolverUrl;

    @Resource
    private BpmInstanceService bpmInstanceService;

    @Autowired
    @Lazy
    private SpDispatchSchemeExecuteService spDispatchSchemeExecuteService;

    @Resource
    private RemoteMessageSendService remoteMessageSendService;

    private static String shipServerAppKey;

    @Value("${dispatch_solver_url}")
    public void setDispatchSolverUrl(String dispatchSolverUrl) {
        this.dispatchSolverUrl = dispatchSolverUrl;
    }

    @Value("${ship_server.host}")
    public void setShipServerHost(String shipServerHost) {
        this.shipServerHost = shipServerHost;
    }

    @Value("${ship_server.app_key}")
    public void setShipServerAppKey(String shipServerAppKey) {
        this.shipServerAppKey = shipServerAppKey;
    }

    private BigDecimal ROUND_NUMBER = new BigDecimal("10000");

    @Autowired
    @Lazy
    private SpArrangeSchemeDetailService spArrangeSchemeDetailService;

    @Autowired
    private SpYearPlanCapacityRecordService spYearPlanCapacityRecordService;

    @Autowired
    private SpPortRouteDistanceService spPortRouteDistanceService;


    /**
     * 获取船只租期
     * @param imo
     * @return
     */
    public ConShipping getShipRentTime(String imo){
        ConShipping conShipping = conShippingMapper.selectOne(
                new LambdaQueryWrapper<ConShipping>()
                .eq(ConShipping::getImo,imo)
                .in(ConShipping::getStatus, 1, 2, 3)
                .orderByDesc(ConShipping::getDeliveryDateBegin)
                .last("limit 1")
        );
        return conShipping;
    }

    /**
     * 获取船货前后空挡天数
     * @param detailListAll
     * @return
     */
    public List<SpDispatchSchemeDetail> getIntervalDays(List<SpDispatchSchemeDetail> detailListAll,Integer year){
        if(detailListAll.size() == 0){
            return detailListAll;
        }
        List<SpDispatchSchemeDetail> saveList = new LinkedList<>();
        Map<Integer,List<SpDispatchSchemeDetail>> yearMap = detailListAll.stream().collect(Collectors.groupingBy(SpDispatchSchemeDetail::getPlanYear));
        for (Integer integer : yearMap.keySet()) {
            List<SpDispatchSchemeDetail> detailListYear = yearMap.get(integer);
            year = integer;

            Date yearBegin = DateUtil.parse(year + "-01-01 00:00:00", "yyyy-MM-dd HH:mm:ss");
            Date yearEnd = DateUtil.parse(year + "-12-31 23:59:59", "yyyy-MM-dd HH:mm:ss");

            Map<String,List<SpDispatchSchemeDetail>> detailMap = detailListYear.stream().collect(Collectors.groupingBy(SpDispatchSchemeDetail::getImo));
            for (String s : detailMap.keySet()) {
                List<SpDispatchSchemeDetail> detailList = detailMap.get(s);
//                ConShipping conShipping = getShipRentTime(s);
//                if(conShipping != null){
//                    if(conShipping.getDeliveryDateBegin().after(yearBegin) && conShipping.getDeliveryDateBegin().before(yearEnd)){
//                        yearBegin = conShipping.getDeliveryDateBegin();
//                    }
//                    if(conShipping.getRedeliveryDateEnd().before(yearEnd) && conShipping.getRedeliveryDateEnd().after(yearBegin)){
//                        yearEnd = conShipping.getRedeliveryDateEnd();
//                    }
//                }
                if(detailList.size() == 1){
                    Integer frontDays = 0;
                    Integer backDays = 0;
                    Integer emptyDays = 0;
                    SpDispatchSchemeDetail schemeDetail = detailList.get(0);
                    if(schemeDetail.getExpectedEmptyReturnWindow().after(yearBegin)){
                        frontDays = Integer.parseInt(String.valueOf(DateUtil.betweenDay(yearBegin,schemeDetail.getExpectedEmptyReturnWindow(),true)));
                    }
                    if(schemeDetail.getExpectedUnloadingWindow().before(yearEnd)){
                        backDays = Integer.parseInt(String.valueOf(DateUtil.betweenDay(schemeDetail.getExpectedUnloadingWindow(),yearEnd,true)));
                    }
                    schemeDetail.setFrontDays(frontDays);
                    schemeDetail.setBackDays(backDays);
                    if(frontDays >= 30){
                        emptyDays = (emptyDays+frontDays);
                    }
                    if(backDays >= 30){
                        emptyDays = (emptyDays+backDays);
                    }
                    schemeDetail.setEmptyDays(emptyDays);
                    schemeDetail.setUnoccupiedDays(emptyDays);
                    saveList.addAll(detailList);
                    continue;
                }
                detailList = detailList.stream().sorted(Comparator.comparing(SpDispatchSchemeDetail::getExpectedEmptyReturnWindow))
                        .collect(Collectors.toList());

                Date lastEndDate = yearBegin;
                for (int i = 0; i < detailList.size(); i++) {
                    SpDispatchSchemeDetail schemeDetail = detailList.get(i);
                    Integer frontDays = 0;
                    Integer backDays = 0;
                    Integer emptyDays = 0;
                    if(i == (detailList.size()-1)){
                        //最后一条
                        frontDays = Integer.parseInt(String.valueOf(DateUtil.betweenDay(lastEndDate,schemeDetail.getExpectedEmptyReturnWindow(),true)));
                        if(schemeDetail.getExpectedUnloadingWindow().before(yearEnd)){
                            backDays = Integer.parseInt(String.valueOf(DateUtil.betweenDay(schemeDetail.getExpectedUnloadingWindow(),yearEnd,true)));
                        }
                        schemeDetail.setFrontDays(frontDays);
                        schemeDetail.setBackDays(backDays);
                        if(frontDays >= 30){
                            emptyDays = (emptyDays+frontDays);
                        }
                        if(backDays >= 30){
                            emptyDays = (emptyDays+backDays);
                        }
                        schemeDetail.setEmptyDays(emptyDays);
                        schemeDetail.setUnoccupiedDays(emptyDays);
                        continue;
                    }

                    SpDispatchSchemeDetail nextSchemeDetail = detailList.get(i+1);

                    if(i == 0){
                        //第一条
                        if(schemeDetail.getExpectedEmptyReturnWindow().after(yearBegin)){
                            frontDays = Integer.parseInt(String.valueOf(DateUtil.betweenDay(yearBegin,schemeDetail.getExpectedEmptyReturnWindow(),true)));
                        }
                        backDays = Integer.parseInt(String.valueOf(DateUtil.betweenDay(schemeDetail.getExpectedUnloadingWindow()
                                ,nextSchemeDetail.getExpectedEmptyReturnWindow(),true)));
                        schemeDetail.setFrontDays(frontDays);
                        schemeDetail.setBackDays(backDays);
                        if(frontDays >= 30){
                            emptyDays = (emptyDays+frontDays);
                        }
                        if(backDays >= 30){
                            emptyDays = (emptyDays+backDays);
                        }

                        lastEndDate = schemeDetail.getExpectedUnloadingWindow();
                        schemeDetail.setEmptyDays(emptyDays);
                        schemeDetail.setUnoccupiedDays(emptyDays);
                        continue;
                    }
                    frontDays = Integer.parseInt(String.valueOf(DateUtil.betweenDay(lastEndDate,schemeDetail.getExpectedEmptyReturnWindow(),true)));
                    backDays = Integer.parseInt(String.valueOf(DateUtil.betweenDay(schemeDetail.getExpectedUnloadingWindow()
                            ,nextSchemeDetail.getExpectedEmptyReturnWindow(),true)));
                    schemeDetail.setFrontDays(frontDays);
                    schemeDetail.setBackDays(backDays);
                    if(frontDays >= 30){
                        emptyDays = (emptyDays+frontDays);
                    }
                    if(backDays >= 30){
                        emptyDays = (emptyDays+backDays);
                    }
                    schemeDetail.setEmptyDays(emptyDays);
                    schemeDetail.setUnoccupiedDays(emptyDays);
                    lastEndDate = schemeDetail.getExpectedUnloadingWindow();
                }
                saveList.addAll(detailList);
            }
        }
        return saveList;
    }

    @Override
    public JsonResult yearPlanSchemeApproveBack(String planId, Integer approveStatus){
        log.error("年度计划下发审批回执信息："+planId+"=========================="+approveStatus);

        SpYearlyPlan spYearlyPlan = spYearlyPlanMapper.selectById(planId);
        if(spYearlyPlan == null){
            return JsonResult.failed("计划信息异常!");
        }


        SpYearPlanScheme spYearPlanScheme = planSchemeMapper.selectOne(
                new LambdaQueryWrapper<SpYearPlanScheme>()
                .eq(SpYearPlanScheme::getPlanId,planId)
                .eq(SpYearPlanScheme::getApproveStatus,0)
                .orderByDesc(SpYearPlanScheme::getCreateDate)
                .last("limit 1")
        );
        if(spYearPlanScheme == null){
            return JsonResult.failed("方案信息异常");
        }

        if(2 == approveStatus){
            //未通过
            spYearPlanScheme.setApproveStatus(2);
            planSchemeMapper.updateById(spYearPlanScheme);
            return JsonResult.success();
        }
        SpYearPlanScheme updateData = new SpYearPlanScheme();
        updateData.setApproveStatus(1);
        planSchemeMapper.update(updateData,new LambdaQueryWrapper<SpYearPlanScheme>().eq(SpYearPlanScheme::getPlanId,spYearPlanScheme.getPlanId()));

        spYearPlanScheme.setIsDistribute(1);
        planSchemeMapper.updateById(spYearPlanScheme);

        List<SpYearPlanSchemeDetail> details = spYearPlanSchemeDetailMapper.selectList(
                new LambdaQueryWrapper<SpYearPlanSchemeDetail>()
                        .eq(SpYearPlanSchemeDetail::getSchemeId,spYearPlanScheme.getId())
        );

        spYearlyPlan.setState(3);

        //如果是方案下发，需要将对应的数据插入调度信息表，默认归属到最新的一条共享调度方案里面
        SpDispatchScheme dispatchScheme = spDispatchService.getBaseMapper().selectOne(
                new LambdaQueryWrapper<SpDispatchScheme>()
                        .eq(SpDispatchScheme::getIsShare,1)
                        .orderByDesc(SpDispatchScheme::getCreateDate)
                        .last("limit 1")
        );
        if(dispatchScheme == null){
            dispatchScheme = new SpDispatchScheme();
            BeanCopyUtils.copy(spYearPlanScheme,dispatchScheme);
            dispatchScheme.setId(null);
            dispatchScheme.setIsDistribute(1);
            dispatchScheme.setNumber(1);
            dispatchScheme.setApproveStatus(1);
            spDispatchService.getBaseMapper().insert(dispatchScheme);
        }

        //数据同步到船货排布里面
        SpArrangeScheme spArrangeScheme = spArrangeSchemeService.getBaseMapper().selectOne(
                new LambdaQueryWrapper<SpArrangeScheme>()
                        .orderByDesc(SpArrangeScheme::getCreateDate)
                        .last("limit 1")
        );
        if(spArrangeScheme == null){
            spArrangeScheme = new SpArrangeScheme();
            BeanCopyUtils.copy(dispatchScheme,spArrangeScheme);
            spArrangeScheme.setId(null);
            spArrangeScheme.setIsDistribute(0);
            spArrangeScheme.setDispatchSchemeId(dispatchScheme.getId());
            spArrangeSchemeService.getBaseMapper().insert(spArrangeScheme);
        }

        //保存详情信息
        for (SpYearPlanSchemeDetail detail : details) {
            SpDispatchSchemeDetail dispatchSchemeDetail = new SpDispatchSchemeDetail();
            BeanCopyUtils.copy(detail,dispatchSchemeDetail);
            dispatchSchemeDetail.setId(null);
            //年度计划下发到调度优化，默认锁定时间，不锁船只
            dispatchSchemeDetail.setLockTime(1);
            dispatchSchemeDetail.setLockShip(0);
            dispatchSchemeDetail.setSchemeId(dispatchScheme.getId());
            dispatchSchemeDetail.setPlanYear(spYearlyPlan.getYear());
            dispatchSchemeDetail.setExecuteStatus(1);
            this.save(dispatchSchemeDetail);


            SpArrangeSchemeDetail arrangeSchemeDetail = new SpArrangeSchemeDetail();
            BeanCopyUtils.copy(dispatchSchemeDetail,arrangeSchemeDetail);
            arrangeSchemeDetail.setId(null);
            arrangeSchemeDetail.setSchemeId(spArrangeScheme.getId());
            arrangeSchemeDetail.setDispatchSchemeDetailId(dispatchSchemeDetail.getId());
            spArrangeSchemeDetailService.save(arrangeSchemeDetail);

            dispatchSchemeDetail.setArrangeSchemeDetailId(arrangeSchemeDetail.getId());
            this.updateById(dispatchSchemeDetail);
        }

        //下发以后需要更新资源方的装载货量信息
        spYearPlanCapacityRecordService.updateCapacityRecord(details);

        // 资源方数量
        Map<String, List<SpYearPlanSchemeDetail>> collect = details.stream().collect(Collectors
                .groupingBy(SpYearPlanSchemeDetail::getSeller));
        Set<String> longs = collect.keySet();
        spYearlyPlan.setResourceSide(longs.size());

        //计划货船数
        spYearlyPlan.setPlannedNumberOfShipments(details.size());

        // 计划装载量
        BigDecimal reduce = details.stream().map(SpYearPlanSchemeDetail::getSingleGoodsQuantity)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        spYearlyPlan.setPlannedLoadingCapacity(reduce);

        // 获取第一艘船装载时间和卸载时间
        SpYearPlanSchemeDetail detailFirst = details.stream().min(Comparator.comparing((SpYearPlanSchemeDetail::getExpectedLoadingDate))).get();
        // 获取最后一艘船的装载时间和卸载时间
        SpYearPlanSchemeDetail detailLast = details.stream().max(Comparator.comparing((SpYearPlanSchemeDetail::getExpectedLoadingDate))).get();

        spYearlyPlan.setFirstTimeLoading(detailFirst.getExpectedLoadingDate());
        spYearlyPlan.setFirstTimeUnloading(detailFirst.getExpectedUnloadingCompletionWindow());
        spYearlyPlan.setLastTimeLoading(detailLast.getExpectedLoadingDate());
        spYearlyPlan.setLastTimeUnloading(detailLast.getExpectedUnloadingCompletionWindow());

        spYearlyPlanMapper.updateById(spYearlyPlan);

        //共享和下发都需要更新班期表
        shipScheduleService.updateShipSchedule(spYearPlanScheme.getId());

        //下发以后需要更新资源方的装载货量信息
        spYearPlanCapacityRecordService.updateCapacityRecord(details);

        //每次下发，都需要根据规则，重新生成船货排布里面的航次号和货号
        spArrangeSchemeService.resetVoyageNoAndGoodsNumberByYear(spYearlyPlan.getYear());

        return JsonResult.success();
    }

    @Resource
    @Lazy
    ProcessInstanceService processInstanceService;
    /**
     *
     * @param param 流程参数
     * @param businessId  业务id
     * @return 返回流程实例id 存储方便流程查询
     */
    public String startProcessInstance(Map<String,Object> param,Long businessId){
        String result = processInstanceService.createProcessInstance2(BpmInstanceService.SP_YEAR_PLAN_BPM_KEY,param,businessId+"");
        return result;
    }

    @Override
    public JsonResult schemeChartCalculateSave(DispatchSchemeChartSaveDto dto) {
        List<SpDispatchSchemeDetail> details = dto.getSchemeSaveData();
        if(details.size() == 0){
            return JsonResult.failed("保存的方案信息不能为空");
        }

        //校验每艘船设置的货运时间是否有冲突
        Map<String,List<SpDispatchSchemeDetail>> boatMap = details.stream().collect(Collectors.groupingBy(SpDispatchSchemeDetail::getShipName));
        for (String boatName : boatMap.keySet()) {
            List<SpDispatchSchemeDetail> boatSchemeDetailList = boatMap.get(boatName);
            //先根据装载时间排序，然后遍历判断，如果当前数据的装载时间时间在上一条数据的返程时间之前，则表示时间窗口有冲突
            boatSchemeDetailList = boatSchemeDetailList.stream().sorted(Comparator.comparing(SpDispatchSchemeDetail::getExpectedLoadingDate)).collect(Collectors.toList());
            Date compareReturnDate = null;
            for (SpDispatchSchemeDetail detail : boatSchemeDetailList) {
                if(detail.getIsFobSell() != null && detail.getIsFobSell().intValue() == 1){
                    continue;
                }

                if(compareReturnDate != null && detail.getExpectedLoadingDate().before(compareReturnDate)){
                    return JsonResult.failed("时间窗口冲突【船名："+boatName+",装载时间："+detail.getExpectedLoadingDate()+"】");
                }
                compareReturnDate = detail.getExpectedEmptyReturnWindow();
            }
        }

        //获取原始方案数据
        SpDispatchScheme spDispatchScheme = spDispatchService.getBaseMapper().selectById(dto.getSchemeId());
        if(spDispatchScheme == null){
            return JsonResult.failed("方案信息异常");
        }

//        if(spDispatchScheme.getIsShare().intValue() == 1 ){
//            //如果当前方案是已经共享了的，需要新增一条方案信息作为个人的，不能影响已经共享出去的方案
//            spDispatchScheme.setId(null);
//            spDispatchScheme.setIsHistory(0);
//            spDispatchScheme.setIsShare(0);
//            spDispatchScheme.setApproveStatus(1);
//            spDispatchScheme.setCreateDate(new Date());
//            spDispatchScheme.setCreateBy(SecurityUtils.getUsername());
//            spDispatchService.getBaseMapper().insert(spDispatchScheme);
//        }

        //重新生成航次号和货号
        details = CommonUtil.resetVoyageNoAndGoodsNumberDispatch(details);
        for (int i = 1; i <= details.size(); i++) {
            SpDispatchSchemeDetail detail = details.get(i-1);
            detail.setSchemeId(spDispatchScheme.getId());
            detail.setCreateDate(new Date());
            detail.setCreateBy(SecurityUtils.getUsername());
            SpInformation spInformation = spInformationService.getBaseMapper().selectOne(
                    new LambdaQueryWrapper<SpInformation>()
                            .eq(SpInformation::getNameOfVessel,detail.getShipName())
                            .last("limit 1")
            );
            if(spInformation != null && !StringUtils.equals(spInformation.getImo(),detail.getImo())){
                //处理船只imo不匹配的情况
                detail.setImo(spInformation.getImo());
            }
            if(StringUtils.isNotBlank(detail.getShipContractName())
                    && detail.getShipContractName().indexOf(detail.getShipName()) < 0){
                updateShipSchedule(detail);
            }
            getDispatchIntervalDaysAndTradeRate(detail,spInformation, null);
        }

        if(details.size() > 0){

            this.updateBatchById(details);

            //重新计算空载时间
            details = resetSetLastEmptyInfo(details,new Date());

            //重新计算一下前后空挡信息
            details = getIntervalDays(details,LocalDate.now().getYear());

            //计算优化成本及船舶利用率
            details = getUseRateAndDispatchCost(details,null);
            this.updateBatchById(details);
        }
        return JsonResult.success();
    }

    @Override
    public List<ConResourceSellerVO> selectAllSellersByScheme() {
        //获取最新的方案信息
        SpDispatchScheme spDispatchScheme = spDispatchService.getBaseMapper().selectOne(
                new LambdaQueryWrapper<SpDispatchScheme>()
                        .eq(SpDispatchScheme::getIsHistory,0)
                        .and(qw -> {
                            qw.eq(SpDispatchScheme::getCreateBy,SecurityUtils.getUsername());
                            qw.or().eq(SpDispatchScheme::getIsShare,1);
                        })
                        .orderByDesc(SpDispatchScheme::getCreateDate)
                        .last("limit 1")
        );
        List<ConResourceSellerVO> resultList = new LinkedList<>();
        if(spDispatchScheme == null){
            return resultList;
        }
        resultList = this.baseMapper.selectAllSellersByScheme(spDispatchScheme.getId());
        return resultList;
    }

    @Override
    public JsonResult contractUpdate() {
//        List<SpYearPlanSchemeDetail> yearPlanList = spYearPlanSchemeDetailService.getBaseMapper().selectList(
//                new LambdaQueryWrapper<SpYearPlanSchemeDetail>()
//                .isNotNull(SpYearPlanSchemeDetail::getShipContractId)
//        );
//        Map<Long,List<SpYearPlanSchemeDetail>> yearMap = yearPlanList.stream().collect(Collectors.groupingBy(SpYearPlanSchemeDetail::getShipContractId));
//        for (Long aLong : yearMap.keySet()) {
//            ConShipping conShipping = conShippingMapper.selectById(aLong);
//            if(conShipping == null){
//                continue;
//            }
//            List<SpYearPlanSchemeDetail> details = yearMap.get(aLong);
//            for (SpYearPlanSchemeDetail detail : details) {
//                detail.setShipContractName(conShipping.getContractName());
//                detail.setStorageCapacity(conShipping.getCabinCapacity());
//                detail.setShipName(conShipping.getShipName());
//                detail.setImo(conShipping.getImo());
//            }
//            spYearPlanSchemeDetailService.updateBatchById(details);
//        }
//        List<SpYearPlanSchemeDetail> yearPlanReList = spYearPlanSchemeDetailService.getBaseMapper().selectList(
//                new LambdaQueryWrapper<SpYearPlanSchemeDetail>()
//                        .isNotNull(SpYearPlanSchemeDetail::getResourceContract)
//        );
//        Map<Long,List<SpYearPlanSchemeDetail>> yearReMap = yearPlanReList.stream().collect(Collectors.groupingBy(SpYearPlanSchemeDetail::getResourceContract));
//        for (Long aLong : yearReMap.keySet()) {
//            ConResourceMemorandum resourceMemorandum = resourceMemorandumMapper.getById(aLong);
//            if(resourceMemorandum == null){
//                continue;
//            }
//            List<SpYearPlanSchemeDetail> details = yearReMap.get(aLong);
//            for (SpYearPlanSchemeDetail detail : details) {
//                detail.setContractName(resourceMemorandum.getContractName());
//                detail.setSeller(resourceMemorandum.getSeller());
//                detail.setBuyer(resourceMemorandum.getBuyer());
//                detail.setTradeBody(resourceMemorandum.getTradeBody());
//                detail.setTradeType(resourceMemorandum.getTradeType());
//            }
//            spYearPlanSchemeDetailService.updateBatchById(details);
//        }



        List<SpDispatchSchemeDetail> disList = this.getBaseMapper().selectList(
                new LambdaQueryWrapper<SpDispatchSchemeDetail>()
                        .isNotNull(SpDispatchSchemeDetail::getImo)
        );
        Map<String,List<SpDispatchSchemeDetail>> disMap = disList.stream().collect(Collectors.groupingBy(SpDispatchSchemeDetail::getImo));
        for (String imo : disMap.keySet()) {
            List<ConShipping> conShippings = conShippingMapper.getByIMO(imo);
            if(conShippings.size() == 0){
                continue;
            }
            ConShipping conShipping = conShippings.get(0);
            List<SpDispatchSchemeDetail> details = disMap.get(imo);
            for (SpDispatchSchemeDetail detail : details) {
                detail.setShipContractName(conShipping.getContractName());
                detail.setStorageCapacity(conShipping.getCabinCapacity());
                detail.setShipName(conShipping.getShipName());
                detail.setImo(conShipping.getImo());
            }
            this.updateBatchById(details);
        }
        List<SpDispatchSchemeDetail> disReList = this.getBaseMapper().selectList(
                new LambdaQueryWrapper<SpDispatchSchemeDetail>()
                        .isNotNull(SpDispatchSchemeDetail::getResourceContract)
        );
        Map<Long,List<SpDispatchSchemeDetail>> disReMap = disReList.stream().collect(Collectors.groupingBy(SpDispatchSchemeDetail::getResourceContract));
        for (Long aLong : disReMap.keySet()) {
            ConResourceMemorandum resourceMemorandum = resourceMemorandumMapper.getById(aLong);
            if(resourceMemorandum == null){
                continue;
            }
            List<SpDispatchSchemeDetail> details = disReMap.get(aLong);
            for (SpDispatchSchemeDetail detail : details) {
                detail.setContractName(resourceMemorandum.getContractName());
                detail.setSeller(resourceMemorandum.getSeller());
                detail.setBuyer(resourceMemorandum.getBuyer());
                detail.setTradeBody(resourceMemorandum.getTradeBody());
                detail.setTradeType(resourceMemorandum.getTradeType());
            }
            this.updateBatchById(details);
        }
        return null;
    }

    @Override
    public JsonResult schemeChartSave(DispatchSchemeChartSaveDto dto) {
        List<SpDispatchSchemeDetail> saveData = dto.getSchemeSaveData();
        if(saveData.size() == 0){
            return JsonResult.failed("保存的方案信息不能为空");
        }

        //获取原始方案数据
        SpDispatchScheme spDispatchScheme = spDispatchService.getBaseMapper().selectById(dto.getSchemeId());
        if(spDispatchScheme == null){
            return JsonResult.failed("方案信息异常");
        }
        //获取全部货物信息
        List<SpDispatchSchemeDetail> allDetails = this.baseMapper.selectList(
                new LambdaQueryWrapper<SpDispatchSchemeDetail>()
                .eq(SpDispatchSchemeDetail::getSchemeId,dto.getSchemeId())
        );

        Map<Long,List<SpDispatchSchemeDetail>> saveDataMap = saveData.stream().collect(Collectors.groupingBy(SpDispatchSchemeDetail::getId));

        List<SpDispatchSchemeDetail> details = new LinkedList<>();
        for (SpDispatchSchemeDetail allDetail : allDetails) {
            if(saveDataMap.get(allDetail.getId()) != null){
                SpDispatchSchemeDetail saveInfo = saveDataMap.get(allDetail.getId()).get(0);
                saveInfo.setExecuteStatus(allDetail.getExecuteStatus());
                details.add(saveInfo);
                continue;
            }
            details.add(allDetail);
        }

        Integer number = null;
        if(dto.getSaveType().intValue() != 1){
            //如果不是个人草稿,则将之前的所有记录都设置为历史记录,当前共享的数据为最新的一条
            SpDispatchScheme updateData = new SpDispatchScheme();
            updateData.setIsHistory(1);
            spDispatchService.getBaseMapper().update(updateData,new LambdaQueryWrapper<>());

//            SpYearPlanScheme maxScheme = spYearlyPlanMapper.selectMaxNumber(SecurityUtils.getStringUserId(), spYearPlanScheme.getPlanId());
            SpDispatchScheme maxScheme = spDispatchService.getBaseMapper().selectOne(
                    new LambdaQueryWrapper<SpDispatchScheme>()
                            .eq(SpDispatchScheme::getIsShare,1)
                            .orderByDesc(SpDispatchScheme::getNumber)
                            .last("limit 1")
            );
            number = 1;
            if (maxScheme != null) {
                number = (maxScheme.getNumber() + 1);
            }
        }
        if(dto.getSaveType().intValue() == 3){
            //下发需要审批，当前有未审批信息的时候不能再次发起
            if(0 == spDispatchScheme.getApproveStatus()){
                throw new ServiceException("当前方案还有未审批的下发，请审批后再发布！", BaseResultCode.GENERAL_ERROR.getCode());
            }
        }

        //新增一条方案信息
        spDispatchScheme.setId(null);
        spDispatchScheme.setIsHistory(0);
        spDispatchScheme.setIsShare(0);
        spDispatchScheme.setNumber(number);

        if(dto.getSaveType().intValue() != 3){
            //不是下发就不需要审批
            spDispatchScheme.setApproveStatus(1);
        }else{
            spDispatchScheme.setIsDistribute(1);
            spDispatchScheme.setApproveStatus(0);
        }
        if(dto.getSaveType().intValue() != 1){
            spDispatchScheme.setIsShare(1);
        }
        spDispatchScheme.setCreateDate(new Date());
        spDispatchScheme.setCreateBy(SecurityUtils.getUsername());
        spDispatchService.getBaseMapper().insert(spDispatchScheme);

        //重新生成航次号和货号
        details = CommonUtil.resetVoyageNoAndGoodsNumberDispatch(details);
        Boolean toApprove = false;
        for (int i = 1; i <= details.size(); i++) {
            SpDispatchSchemeDetail detail = details.get(i-1);
            Long id = detail.getId();
            detail.setSchemeId(spDispatchScheme.getId());
            detail.setCreateDate(new Date());
            detail.setLastDetailId(id);
            detail.setCreateBy(SecurityUtils.getUsername());
            detail.setId(null);
            SpInformation spInformation = spInformationService.getBaseMapper().selectOne(
                    new LambdaQueryWrapper<SpInformation>()
                            .eq(SpInformation::getNameOfVessel,detail.getShipName())
                            .last("limit 1")
            );
            if(spInformation != null && !StringUtils.equals(spInformation.getImo(),detail.getImo())){
                //处理船只imo不匹配的情况
                detail.setImo(spInformation.getImo());
            }
            if(StringUtils.isNotBlank(detail.getShipContractName())
                    && detail.getShipContractName().indexOf(detail.getShipName()) < 0){
                updateShipSchedule(detail);
            }

//            if(detail.getSchemeApprove() != null && detail.getSchemeApprove().intValue() == 1){
//                toApprove = true;
//            }
            if(spInformation != null){
                getDispatchIntervalDaysAndTradeRate(detail,spInformation, null);
            }
        }

        if(details.size() > 0){

            this.saveBatch(details);

            //重新计算空载时间
            details = resetSetLastEmptyInfo(details,new Date());

            //重新计算一下前后空挡信息
            details = getIntervalDays(details,LocalDate.now().getYear());

            //计算优化成本及船舶利用率
            details = getUseRateAndDispatchCost(details,null);
            this.updateBatchById(details);
        }

        if(dto.getSaveType().intValue() == 3){
            if(toApprove){
                //只有有需要审批的，当前操作就需要审批
                ProcessInstanceParam param = new ProcessInstanceParam();
                param.setBpmKey(bpmInstanceService.SP_DISPATCH_SCHEME_BPM_KEY);
                param.setBussinessId(spDispatchScheme.getId());
                Map<String,Object> map = new HashMap<>();
                map.put("moduleName","调度优化");
                map.put("reason","");
                param.setParams(map);
                bpmInstanceService.startProcessInstance(param);
            }else{
                spDispatchService.dispatchSchemeApproveBack(String.valueOf(spDispatchScheme.getId()),1);
            }
            return JsonResult.success("方案发布已提交审批！");
        }
        return JsonResult.success();
    }

    /**
     * 获取贸易利润率及空挡天数
     * @param schemeDetail
     * @param shipInfo
     */
    private void getDispatchIntervalDaysAndTradeRate(SpDispatchSchemeDetail schemeDetail
            ,SpInformation shipInfo,ConResourceMemorandum memorandum){

        if(memorandum == null && schemeDetail.getResourceContract() != null){
            memorandum = resourceMemorandumMapper.selectById(schemeDetail.getResourceContract());
        }
        //实际交付量 = 预计装载量-预计装载量*（时间差天数）*蒸发率-留底量 如果是虚拟船就默认等于预计装载量
        BigDecimal realityUnload = schemeDetail.getSingleGoodsQuantity();
        //蒸发率
        schemeDetail.setEvaporationRate(shipInfo.getEvaporationRate() != null ? shipInfo.getEvaporationRate() : new BigDecimal("0.1"));
        BigDecimal subDec = BigDecimal.ZERO;
        if(shipInfo.getEvaporationRate() != null){
            subDec = schemeDetail.getSingleGoodsQuantity().multiply(schemeDetail.getEvaporationRate()).divide(new BigDecimal("100"));
        }
        //蒸发量
        schemeDetail.setExpectedEvaporationQuantity(subDec);
        realityUnload = schemeDetail.getSingleGoodsQuantity().subtract(subDec);

        //预计卸载量
        schemeDetail.setExpectedUnloadGoodsQuantity(realityUnload);
        //实际卸载量
        schemeDetail.setRealityUnloadingCapacity(realityUnload);

        //涉及成本计算的字段单位都是万美元
        BigDecimal defUnitValue = new BigDecimal("10000");
        BigDecimal defZeroValue = BigDecimal.ZERO;

        schemeDetail.setLoadPassCost(defZeroValue);
        schemeDetail.setLoadPortCost(defZeroValue);
        schemeDetail.setLoadOtherCost(defZeroValue);
        schemeDetail.setUnloadPassCost(defZeroValue);
        schemeDetail.setUnloadPortCost(defZeroValue);
        schemeDetail.setUnloadOtherCost(defZeroValue);
        schemeDetail.setExpectedBottomQuantity(defZeroValue);

        //实际交付价格 = 合同价格
        if(memorandum != null && memorandum.getContractPrice() != null){
            //预计采购价格
            schemeDetail.setConPrice(memorandum.getContractPrice());
            //预计采购成本 计划装载量*预计采购单价
            schemeDetail.setConPriceCost(schemeDetail.getConPrice().multiply(schemeDetail.getSingleGoodsQuantity()).divide(defUnitValue,2,RoundingMode.UP));
            //实际采购单价 默认为预计采购单价
            schemeDetail.setRealityConPrice(schemeDetail.getConPrice());
            //实际采购成本 实际装载量*实际采购单价
            BigDecimal realityConPriceCost = schemeDetail.getRealityConPrice().multiply(schemeDetail.getSingleGoodsQuantity());
            schemeDetail.setRealityConPriceCost(realityConPriceCost.divide(defUnitValue,2,RoundingMode.UP));
            //实际总装载成本=运河费（装载）+港口费（装载）+其他费用（装载）+实际采购成本
            schemeDetail.setRealityLoadTotalCost((realityConPriceCost.add(schemeDetail.getLoadPassCost())
                    .add(schemeDetail.getLoadPortCost()).add(schemeDetail.getLoadOtherCost())).divide(defUnitValue,2,RoundingMode.UP));
        }else{
            schemeDetail.setConPrice(defZeroValue);
            //预计采购成本 计划装载量*预计采购单价
            schemeDetail.setConPriceCost(defZeroValue);
            //实际采购单价 默认为预计采购单价
            schemeDetail.setRealityConPrice(defZeroValue);
            //实际采购成本 实际装载量*实际采购单价
            schemeDetail.setRealityConPriceCost(defZeroValue);
            //实际总装载成本=运河费（装载）+港口费（装载）+其他费用（装载）+实际采购成本
            schemeDetail.setRealityLoadTotalCost(defZeroValue);
        }

        /**
         * 年度计划默认都是回国，根据贸易利润为0来推算国内销售价格
         * 回国：贸易利润 = 实际交付量（预计卸载量）*实际交付价格（实际销售的价格）
         * +其他收入（为0）-其他交付成本（为0）-实际装载量（预计装载量）*实际结算单价（长协合同里面的合同价格）-其他费用（为0）
         */

        //国际贸易利润
        schemeDetail.setNationalTradingProfit(defZeroValue);
        BigDecimal sellPrice = BigDecimal.ZERO;
        if(schemeDetail.getConPrice().compareTo(BigDecimal.ZERO) > 0){
            sellPrice = (schemeDetail.getSingleGoodsQuantity().multiply(schemeDetail.getConPrice()))
                    .divide(schemeDetail.getRealityUnloadingCapacity(),2,RoundingMode.HALF_UP);
        }
        //预计销售价格
        schemeDetail.setSellPrice(sellPrice);
        //预计销售收入
        schemeDetail.setExpectSellMoney(schemeDetail.getSellPrice().multiply(schemeDetail.getExpectedUnloadGoodsQuantity()).divide(defUnitValue,2,RoundingMode.UP));
        //实际销售价格
        schemeDetail.setRealitySellPrice(sellPrice);
        //预计销售收入
        schemeDetail.setRealitySellMoney(schemeDetail.getRealitySellPrice().multiply(schemeDetail.getRealityUnloadingCapacity()).divide(defUnitValue,2,RoundingMode.UP));
        //其他收入
        schemeDetail.setOtherGetMoney(defZeroValue);
        //补货利润
        schemeDetail.setRestockProfit(defZeroValue);
        //船舶优化成本
        schemeDetail.setDispatchCost(defZeroValue);
        //船舶利用率
        schemeDetail.setShipUseRate(defZeroValue);
        //可优化天数
        schemeDetail.setUnoccupiedDays(0);
        //实际总交付成本=运河费（卸载）+港口费（卸载）+其他费用（卸载)
        schemeDetail.setRealityTotalDeliverCost((schemeDetail.getUnloadPassCost()
                .add(schemeDetail.getUnloadPortCost()).add(schemeDetail.getUnloadOtherCost())).divide(defUnitValue,2,RoundingMode.UP));
        //贸易利润率=国际贸易利润+补货利润+船舶优化成本
        schemeDetail.setTradingProfit(schemeDetail.getNationalTradingProfit().add(schemeDetail.getRestockProfit()).add(schemeDetail.getDispatchCost()));
    }

    @Override
    public JsonResult schemeRollback(String schemeId) {
        //获取要还原的方案数据
        SpYearPlanScheme spYearPlanScheme = planSchemeMapper.selectById(schemeId);
        if(spYearPlanScheme == null){
            return JsonResult.failed("方案信息异常");
        }

        //校验方案下发状态
        SpYearlyPlan spYearlyPlan = spYearlyPlanMapper.selectById(spYearPlanScheme.getPlanId());
        if(spYearlyPlan.getState() != null && spYearlyPlan.getState().intValue() == 3){
            return JsonResult.failed("当前方案年度计划已生效,操作失败!");
        }

        SpYearPlanScheme updateData = new SpYearPlanScheme();
        updateData.setIsHistory(1);
        planSchemeMapper.update(updateData,new LambdaQueryWrapper<SpYearPlanScheme>().eq(SpYearPlanScheme::getPlanId,spYearPlanScheme.getPlanId()));


        //获取方案对应的详情信息
        List<SpYearPlanSchemeDetail> details = spYearPlanSchemeDetailMapper.selectList(
                new LambdaQueryWrapper<SpYearPlanSchemeDetail>()
                .eq(SpYearPlanSchemeDetail::getSchemeId,spYearPlanScheme.getId())
        );
        if(details.size() == 0){
            return JsonResult.failed("方案详细信息异常");
        }
        //新增一条方案信息
        spYearPlanScheme.setId(null);
        spYearPlanScheme.setIsHistory(0);
        spYearPlanScheme.setIsShare(0);
        spYearPlanScheme.setCreateDate(new Date());
        spYearPlanScheme.setCreateBy(SecurityUtils.getUsername());
        planSchemeMapper.insert(spYearPlanScheme);

        //保存方案详情信息
        for (SpYearPlanSchemeDetail detail : details) {
            detail.setSchemeId(spYearPlanScheme.getId());
            detail.setCreateDate(new Date());
            detail.setCreateBy(SecurityUtils.getUsername());
            detail.setId(null);
        }
        spYearPlanSchemeDetailService.saveBatch(details);

        return JsonResult.success();
    }

    @Override
    public JsonResult<List<SpYearPlanSchemeDetail>> getSchemeDetailHistory(SchemeChartSimpleDto dto) {
        if(dto.getSchemeDetailId() == null){
            return JsonResult.failed("方案详情ID不能为空！");
        }
        List<SpYearPlanSchemeDetail> resultList = spYearPlanSchemeDetailMapper.getSchemeDetailHistory(dto);
        if(resultList.size() == 0){
            SpYearPlanSchemeDetail detail = spYearPlanSchemeDetailMapper.selectById(dto.getSchemeDetailId());
            if(detail != null){
                resultList.add(detail);
            }
        }
        return JsonResult.success(resultList);
    }

    @Override
    public JsonResult schemeDetailUpdate(SpDispatchSchemeDetailBO dto) {
        if(dto.getId() == null){
            return JsonResult.failed("详情ID不能为空！");
        }
        SpDispatchSchemeDetail schemeDetail = this.getBaseMapper().selectById(dto.getId());
        if(schemeDetail == null){
            throw new BaseException("方案信息异常！");
        }

        Boolean isUpdate = false;
        if(dto.getExpectedLoadingPorts() != null && dto.getExpectedUnloadingPorts() != null
                && schemeDetail.getExpectedLoadingPortId() != null && schemeDetail.getExpectedUnloadingPortId() != null){
            if(!dto.getExpectedLoadingPorts().contains(String.valueOf(schemeDetail.getExpectedLoadingPortId()))
                    || !dto.getExpectedUnloadingPorts().contains(String.valueOf(schemeDetail.getExpectedUnloadingPortId()))){
                isUpdate = true;
            }
        }

        if(dto.getSellerResourceContract() != null
                && (schemeDetail.getSellerResourceContract() == null || dto.getSellerResourceContract().longValue() != schemeDetail.getSellerResourceContract())){
            //绑定销售合同
            ConResourceMemorandum resourceMemorandum = resourceMemorandumMapper.getById(dto.getSellerResourceContract());
            if(resourceMemorandum == null){
                return JsonResult.failed("销售合同信息异常！");
            }
            dto.setSellPrice(resourceMemorandum.getContractPrice());
            dto.setSellerResourceContract(resourceMemorandum.getId());
            dto.setSellerContractName(resourceMemorandum.getContractName());
            dto.setSellerTradeType(resourceMemorandum.getTradeType());
            dto.setSellerTradeBody(resourceMemorandum.getTradeBody());
            if(resourceMemorandum.getContractPrice() != null){
                //贸易利润率=（销售价格*实际卸载量）-（实际装载量*合同价格）
                BigDecimal tradingProfit = BigDecimal.ZERO;
                if(dto.getSingleGoodsQuantity() != null && dto.getConPrice() != null && dto.getRealityUnloadingCapacity() != null){
                    tradingProfit = dto.getSellPrice().multiply(dto.getRealityUnloadingCapacity())
                            .subtract(dto.getSingleGoodsQuantity().multiply(dto.getConPrice()));
                }
                tradingProfit = tradingProfit.divide(new BigDecimal("10000"),2,RoundingMode.HALF_UP);
                dto.setTradingProfit(tradingProfit);
            }
            if(resourceMemorandum.getDeliveryType() != null && resourceMemorandum.getDeliveryType().intValue() == 1){
                if(resourceMemorandum.getTradeModel() != null && (resourceMemorandum.getTradeModel().intValue() == 2 || resourceMemorandum.getTradeModel().intValue() == 3)){
                    dto.setIsFobSell(1);
                }
            }
            if(resourceMemorandum.getContractName().indexOf("返装销售") > -1 && dto.getVoyageNo().indexOf("R") < 0){
                dto.setVoyageNo(dto.getVoyageNo()+"R");
            }
            if(resourceMemorandum.getContractName().indexOf("返装销售") > -1 && dto.getGoodsNum().indexOf("R") < 0){
                dto.setGoodsNum(dto.getGoodsNum()+"R");
            }
        }

        if(isUpdate) {
            List<Long> startPortList = dto.getExpectedLoadingPorts().stream()
                    .map(Long::valueOf)
                    .collect(Collectors.toList());
            List<Long> endPortList = dto.getExpectedUnloadingPorts().stream()
                    .map(Long::valueOf)
                    .collect(Collectors.toList());
            ShipLoadDaysAndRouteVo shipLoadDaysAndRouteVo = spPortRouteDistanceService.getCommonLoadDay(startPortList
                    , endPortList, dto.getShipSpeed(), dto.getPassStrait(), dto.getPassArea());

            if (shipLoadDaysAndRouteVo == null) {
                throw new BaseException("暂无航线信息！");
            }
            //装载港
            dto.setExpectedLoadingPort(shipLoadDaysAndRouteVo.getStartPortName());
            dto.setExpectedLoadingPortId(shipLoadDaysAndRouteVo.getStartPortId());
            //卸载港
            dto.setExpectedUnloadingPort(shipLoadDaysAndRouteVo.getEndPortName());
            dto.setExpectedUnloadingPortId(shipLoadDaysAndRouteVo.getEndPortId());
            dto.setPassStrait(shipLoadDaysAndRouteVo.getPassStrait());
            dto.setPassArea(shipLoadDaysAndRouteVo.getPassArea());
            dto.setExpectedLoadThirdId(shipLoadDaysAndRouteVo.getStartThirdPortId());
            dto.setExpectedUnloadThirdId(shipLoadDaysAndRouteVo.getEndThirdPortId());
            dto.setUnloadDistance(shipLoadDaysAndRouteVo.getTrackDistance());

            int shipDays = 0;
            int chinaDays = 0;
            int europeDays = 0;
            int indiaDays = 0;

            if (shipLoadDaysAndRouteVo.getShipDay() != null) {
                shipDays = shipLoadDaysAndRouteVo.getShipDay();
            }
            if (shipLoadDaysAndRouteVo.getChinaDays() != null) {
                chinaDays = shipLoadDaysAndRouteVo.getChinaDays();
            }
            if (shipLoadDaysAndRouteVo.getEuropeDays() != null) {
                europeDays = shipLoadDaysAndRouteVo.getEuropeDays();
            }
            if (shipLoadDaysAndRouteVo.getIndiaDays() != null) {
                indiaDays = shipLoadDaysAndRouteVo.getIndiaDays();
            }

            Date expectLoadDate = dto.getExpectedLoadingDate();
            dto.setExpectedLoadingDate(expectLoadDate);
            dto.setExpectedLoadingCompletionWindow(DateUtil.offsetDay(expectLoadDate, 2));

            //预计卸载时间
            dto.setExpectedUnloadingWindow(DateUtil.offsetDay(expectLoadDate, shipDays));
            //中国预计卸载时间
            dto.setChinaExpectedUnloadingWindow(DateUtil.offsetDay(expectLoadDate, chinaDays));
            //印度预计卸载时间
            dto.setIndiaUnloadTime(DateUtil.offsetDay(expectLoadDate, indiaDays));
            //欧洲预计卸载时间
            dto.setEuropeUnloadTime(DateUtil.offsetDay(expectLoadDate, europeDays));
            //预计满载航程天数
            dto.setExpectedFullLoadVoyageDays(shipDays);
            //印度空载航程天数
            dto.setIndiaEmptyLoadDays(indiaDays);
            //欧洲空载航程天数
            dto.setEuropeEmptyLoadDays(europeDays);
            //预计卸载完成窗口
            dto.setExpectedUnloadingCompletionWindow(DateUtil.offsetDay(dto.getExpectedUnloadingWindow(), 2));

            //预计空载返程时间
            dto.setExpectedEmptyReturnWindow(DateUtil.offsetDay(expectLoadDate, ~shipDays + 1));
            //中国预计空载返程窗口
            dto.setChinaExpectedEmptyReturnWindow(DateUtil.offsetDay(expectLoadDate, ~chinaDays + 1));
            //欧洲预计空载返程窗口
            dto.setEuropeExpectedEmptyReturnWindow(DateUtil.offsetDay(expectLoadDate, ~europeDays + 1));
            //印度预计空载返程窗口
            dto.setIndiaExpectedEmptyReturnWindow(DateUtil.offsetDay(expectLoadDate, ~indiaDays + 1));

            //预计空载航程天数
            SpDispatchSchemeDetail lastSchemeDetail = this.getBaseMapper().selectOne(
                    new LambdaQueryWrapper<SpDispatchSchemeDetail>()
                            .lt(SpDispatchSchemeDetail::getExpectedLoadingDate,dto.getExpectedLoadingDate())
                            .eq(SpDispatchSchemeDetail::getSchemeId,dto.getSchemeId())
                            .eq(SpDispatchSchemeDetail::getImo,dto.getImo())
                            .orderByDesc(SpDispatchSchemeDetail::getExpectedLoadingDate)
                            .last("limit 1")
            );
            dto.setLastShipSpeed(dto.getShipSpeed());
            if(lastSchemeDetail == null){
                //没有上一船货，则没有空载时间
                dto.setExpectedEmptyLoadVoyageDays(0);
                dto.setExpectedEmptyReturnWindow(dto.getExpectedLoadingDate());

                //没有空载时间则清空空载航线信息
                dto.setEmptyDistance(0);
                dto.setLastUnloadPortName("");
            }else{
                ShipRouteSearchVo routeDay = spPortRouteDistanceService.getShipRouteDayByThirdId(dto.getShipSpeed(),lastSchemeDetail.getExpectedUnloadThirdId()
                        ,dto.getExpectedLoadThirdId(),null,null);
                if(routeDay != null){
                    int day = routeDay.getShipRouteDay().intValue();
                    dto.setEmptyDistance(routeDay.getTrackDistance());
                    dto.setLastUnloadPortName(lastSchemeDetail.getExpectedUnloadingPort());
                    dto.setExpectedEmptyLoadVoyageDays(day);
                    dto.setExpectedEmptyReturnWindow(DateUtil.offsetDay(schemeDetail.getExpectedLoadingDate(), ~day + 1));
                }else{
                    dto.setEmptyDistance(0);
                    dto.setLastUnloadPortName("");
                    dto.setExpectedEmptyLoadVoyageDays(0);
                    dto.setExpectedEmptyReturnWindow(schemeDetail.getExpectedLoadingDate());
                }
            }

        }

        BeanCopyUtils.copy(dto,schemeDetail);

        //重新计算空载和满载航速、时间
        String emptyDays = String.valueOf(DateUtil.betweenDay(schemeDetail.getExpectedEmptyReturnWindow(),schemeDetail.getExpectedLoadingDate(),true));
        schemeDetail.setExpectedEmptyLoadVoyageDays(Integer.parseInt(emptyDays));
        BigDecimal emptySpeed = new BigDecimal(String.valueOf(schemeDetail.getEmptyDistance()))
                .divide(new BigDecimal(emptyDays),2,RoundingMode.HALF_UP)
                .divide(new BigDecimal("24"),2,RoundingMode.HALF_UP);
        schemeDetail.setLastShipSpeed(emptySpeed);

        String fullDays = String.valueOf(DateUtil.betweenDay(schemeDetail.getExpectedLoadingDate(),schemeDetail.getExpectedUnloadingWindow(),true));
        schemeDetail.setExpectedFullLoadVoyageDays(Integer.parseInt(fullDays));
        BigDecimal fullSpeed = new BigDecimal(String.valueOf(schemeDetail.getUnloadDistance()))
                .divide(new BigDecimal(fullDays),2,RoundingMode.HALF_UP)
                .divide(new BigDecimal("24"),2,RoundingMode.HALF_UP);
        schemeDetail.setShipSpeed(fullSpeed);

        this.getBaseMapper().updateById(schemeDetail);

        if(isUpdate){
            //重新生成航次号和货号
            List<SpDispatchSchemeDetail> detailList = this.baseMapper.selectList(
                    new LambdaQueryWrapper<SpDispatchSchemeDetail>()
                            .eq(SpDispatchSchemeDetail::getSchemeId, schemeDetail.getSchemeId())
                            .ge(SpDispatchSchemeDetail::getExpectedLoadingDate, DateUtils.parseDateToStr("yyyy-MM-dd",schemeDetail.getExpectedLoadingDate()))
            );
            if (detailList.size() > 0) {
                detailList = CommonUtil.resetVoyageNoAndGoodsNumberDispatch(detailList);

                detailList = resetSetLastEmptyInfo(detailList,schemeDetail.getExpectedLoadingDate());

                //重新计算一下前后空挡信息
                detailList = getIntervalDays(detailList,null);

                //计算优化成本及船舶利用率
                detailList = getUseRateAndDispatchCost(detailList,null);

                this.updateBatchById(detailList);
            }
        }

        if(schemeDetail.getArrangeSchemeDetailId() != null){
            //更新船货里面的数据
            SpArrangeSchemeDetail arrangeSchemeDetail = spArrangeSchemeDetailService.getById(schemeDetail.getArrangeSchemeDetailId());
            if(arrangeSchemeDetail != null){
                SpArrangeSchemeDetail saveData = new SpArrangeSchemeDetail();
                BeanCopyUtils.copy(schemeDetail,saveData);
                saveData.setId(arrangeSchemeDetail.getId());
                spArrangeSchemeDetailService.updateById(saveData);

                SpDispatchSchemeExecute execute = spDispatchSchemeExecuteService.getBaseMapper().selectOne(
                        new LambdaQueryWrapper<SpDispatchSchemeExecute>()
                                .eq(SpDispatchSchemeExecute::getArrangeSchemeDetailId,schemeDetail.getArrangeSchemeDetailId())
                                .last("limit 1")
                );
                if(execute != null){
                    Long executeId = execute.getId();
                    BeanCopyUtils.copy(schemeDetail,execute);
                    execute.setId(executeId);
                    spDispatchSchemeExecuteService.updateById(execute);

                    //执行中的货物需要进行消息提醒
                    SysMessageSend messageSend = new SysMessageSend();
                    messageSend.setMessageUrl(ModuleUrlEnum.CYZXXQ.getUrl()+"processInstanceId="+execute.getProcessInstanceId()+"&id="+execute.getId()+"&schemeDetailId="+execute.getSchemeDetailId());
                    messageSend.setMessageType(MessageTypeEnum.MESSAGE_TYPE_23.getCode());
                    messageSend.setContentParam(new String[]{execute.getVoyageNo()});
                    //messageSend.setCompareType(5);
                    List<String> users = Arrays.stream(execute.getAssigeners().split(",")).collect(Collectors.toList());
                    messageSend.setOtherUsernameList(users);
                    remoteMessageSendService.messageSend(messageSend);
                }
            }
        }

        return JsonResult.success();

    }

    @Override
    public JsonResult schemeDetailDelete(Long detailId,String reasonMsg) {
        SpDispatchSchemeDetail spDispatchSchemeDetail = this.getBaseMapper().selectById(detailId);
        if(spDispatchSchemeDetail == null){
            throw new BaseException("方案信息异常！");
        }

        if(spDispatchSchemeDetail.getDeleteApprove() != null && spDispatchSchemeDetail.getDeleteApprove().intValue() == 0){
            throw new BaseException("当前删除操作正在审批中！");
        }

        if(spDispatchSchemeDetail.getExecuteStatus() != null && spDispatchSchemeDetail.getExecuteStatus().intValue() > 1){
            //如果方案已下发，删除需要走审批
            spDispatchSchemeDetail.setDeleteApprove(0);
            this.baseMapper.updateById(spDispatchSchemeDetail);

            //开启审批流程
            Map<String,Object> map = new HashMap<>();
            map.put("serviceType",1);
            map.put("module","dispatchGoodsDelete");
            map.put("moduleName","调度船货删除");
            processInstanceService.createProcessInstance2(BpmInstanceService.SYSTEM_DELETE_BPM_KEY,map,detailId+"");
            return JsonResult.success("已成功发起审批");
        }else{
            this.baseMapper.deleteById(detailId);
        }
        return JsonResult.success();
    }

    @Override
    @Transactional
    public void dataDeleteRollback(String businessKey,int status) {
        SpDispatchSchemeDetail spDispatchSchemeDetail = this.getBaseMapper().selectById(businessKey);
        if(spDispatchSchemeDetail == null){
            return;
        }
        if(status == 1){
            //删除成功
            this.baseMapper.deleteById(businessKey);
        }else{
            //拒绝
            spDispatchSchemeDetail.setDeleteApprove(2);
            this.baseMapper.updateById(spDispatchSchemeDetail);
        }
    }


    @Override
    public JsonResult schemeSave(SpDispatchSchemeSaveModel dto) {
        if(dto.getSchemeDetailList() == null || dto.getSchemeDetailList().size() == 0){
            return JsonResult.failed("方案信息不能为空！");
        }
        //获取当前可用船只信息
        Map<String,List<SpInformation>> shipsMap = spInformationService.getConSpInformation(new Date());
        //可用正常船只
        List<SpInformation> normalShips = shipsMap.get("normalShips");
        //可用的虚拟船只
        List<SpInformation> virtualShips = shipsMap.get("virtualShips");

        if(normalShips.size()+virtualShips.size() == 0){
            //创建虚拟船
            SpInformation virtualShip = spInformationService.createVirtualSpInformation();
            virtualShips.add(virtualShip);
        }

        //获取个人当前调度方案信息
        SpDispatchScheme spDispatchScheme = spDispatchService.getBaseMapper().selectOne(
                new LambdaQueryWrapper<SpDispatchScheme>()
                        .eq(SpDispatchScheme::getIsHistory,0)
                        .and(qw -> {
                            qw.eq(SpDispatchScheme::getCreateBy, SecurityUtils.getUsername());
                            qw.or().eq(SpDispatchScheme::getIsShare,1);
                        })
                        .orderByDesc(SpDispatchScheme::getCreateDate)
                        .last("limit 1")
        );
        if(spDispatchScheme == null){
            //没有发布过年度计划则新增
            spDispatchScheme = new SpDispatchScheme();
            spDispatchScheme.setId(IDUtil.nextId());
            spDispatchScheme.setCreateDate(new Date());
            spDispatchScheme.setCreateBy(SecurityUtils.getUsername());
            spDispatchScheme.setNumber(1);
            spDispatchScheme.setIsShare(1);
            spDispatchScheme.setIsHistory(0);
            spDispatchScheme.setIsDistribute(1);
            spDispatchService.save(spDispatchScheme);
        }
        String tipString = "";
        for (SpDispatchSchemeDetailModel schemeModel : dto.getSchemeDetailList()) {
            if(schemeModel.getSellerConId() == null && schemeModel.getSellerResourceContract() != null){
                schemeModel.setSellerConId(schemeModel.getSellerResourceContract());
            }
            // 查询当年年度计划
            Integer year = DateUtil.year(schemeModel.getExpectedLoadingDate());
            //单位为立方米时转为百万英热
            if (schemeModel.getQuantityUnit() != null && schemeModel.getQuantityUnit().equals("2")) {
                BigDecimal goodsQuantityNew = schemeModel.getGoodsQuantity().multiply(BigDecimal.valueOf(0.985))
                        .subtract(BigDecimal.valueOf(500)).multiply(BigDecimal.valueOf(22.5))
                        .setScale(0, BigDecimal.ROUND_DOWN);
                schemeModel.setGoodsQuantity(goodsQuantityNew);
            }

            List<SpInformation> spInformation = new LinkedList<>();

            ConResourceMemorandum resourceMemorandum = null;
            // 根据条件筛选-判断资源合同中装卸量是否满足船只
            if(schemeModel.getConId() != null){
                resourceMemorandum = resourceMemorandumMapper.selectById(schemeModel.getConId());
                if(resourceMemorandum == null){
                    return JsonResult.failed("合同信息异常！");
                }
            }

            //是否是直接使用的虚拟船
            Boolean useVirtualShip = false;
            if(normalShips.size() > 0){
                for (SpInformation ship : normalShips) {
                    // 根据条件筛选-判断船舱容是否为空
                    if (ship.getStorageCapacity() == null) {
                        continue;
                    }
                    // 根据条件筛选-判断IMO
                    if (schemeModel.getImo() != null && schemeModel.getImo().size() > 0) {
                        if(!schemeModel.getImo().contains(ship.getImo())){
                            //IMO不一致
                            continue;
                        }
                    }
                    // 根据条件筛选-判断船旗国
                    if (schemeModel.getFlagState() != null && schemeModel.getFlagState().size() > 0) {
                        if(!schemeModel.getFlagState().contains(ship.getCountry())){
                            //船旗国不一致
                            continue;
                        }
                    }
                    // 根据条件筛选-判断油耗
                    if (schemeModel.getOilConsumption() != null) {
                        if (ship.getOilConsumption().compareTo(schemeModel.getOilConsumption()) < 0) {
                            continue;
                        }
                    }

                    BigDecimal mut = ship.getStorageCapacity().multiply(BigDecimal.valueOf(0.985))
                            .subtract(BigDecimal.valueOf(500)).multiply(BigDecimal.valueOf(22.5))
                            .setScale(0, BigDecimal.ROUND_DOWN);
//                    if (resourceMemorandum != null && resourceMemorandum.getLoadingOrUnloadingCapacity().compareTo(mut) < 0) {
//                        //合同限定的装卸载量必须大于船只的舱容（这样才能保证我排的船肯定可以把货拉回来）
//                        continue;
//                    }

                    // 根据条件筛选-判断船型起止
                    if (schemeModel.getShipModelStart() != null) {
                        if (schemeModel.getShipModelStart().compareTo(ship.getStorageCapacity()) > 0) {
                            continue;
                        }
                    }
                    if (schemeModel.getShipModelEnd() != null) {
                        if (ship.getStorageCapacity().compareTo(schemeModel.getShipModelStart()) < 0) {
                            continue;
                        }
                    }
                    spInformation.add(ship);
                }
                if(spInformation.size() == 0){
                    //所有船只都不符合条件则选择虚拟船来进行分配
                    useVirtualShip = true;
                    spInformation.addAll(virtualShips);
                }
            }else{
                useVirtualShip = true;
                spInformation.addAll(virtualShips);
            }
            // 处理大船和小船
            List<SpInformation> maxShips = new ArrayList<>();
            List<SpInformation> minShips = new ArrayList<>();
            for (SpInformation sp : spInformation) {
                if (sp.getStorageCapacity().compareTo(new BigDecimal(160000)) > 0) {
                    maxShips.add(sp);
                } else {
                    minShips.add(sp);
                }
            }
            //大小船根据舱容来排序（倒序）
            maxShips = maxShips.stream().sorted(Comparator.comparing(SpInformation::getStorageCapacity).reversed()).collect(Collectors.toList());
            minShips = minShips.stream().sorted(Comparator.comparing(SpInformation::getStorageCapacity).reversed()).collect(Collectors.toList());

            BigDecimal allGoodsQuantity = schemeModel.getGoodsQuantity();

            //剩余货量
            BigDecimal goodsQuantity = schemeModel.getGoodsQuantity();

            List<SpDispatchSchemeDetail> insertData = new LinkedList<>();
            Date loadDate = schemeModel.getExpectedLoadingDate();

            ShipLoadDaysAndRouteVo loadDays = spPortRouteDistanceService.getCommonLoadDay(schemeModel.getLoadingSort()
                    ,schemeModel.getUnloadSort(),schemeModel.getSpeed(),schemeModel.getPassStrait(),schemeModel.getPassArea());
            if(loadDays == null){
                return JsonResult.failed("当前航线暂无可匹配的路线信息！");
            }

            SpDispatchSchemeDetail schemeDetail = initDispatchSchemeDetail(year,resourceMemorandum
                    ,spDispatchScheme.getId(),loadDate,schemeModel,loadDays);
            schemeDetail.setPlanId(spDispatchScheme.getPlanId());

            int month = DateUtil.month(loadDate) + 1;

            SpInformation loadShipInfo = null;
            //根据装载月份来判断大小船的优先级
            if (month >= 4 && month <= 10) {
                //优先大船
                if(maxShips.size() > 0){
                    for (SpInformation ship : maxShips) {
                        if (schemeModel.getImo() != null && schemeModel.getImo().size() > 0
                                && !schemeModel.getImo().contains(ship.getImo())) {
                            continue;
                        }
                        if(checkShipTimeWindows(ship,schemeDetail)){
                            loadShipInfo = ship;
                            break;
                        }
                    }
                }
                if(loadShipInfo == null && minShips.size() > 0){
                    //大船不符合，则派小船
                    for (SpInformation ship : minShips) {
                        if (schemeModel.getImo() != null && schemeModel.getImo().size() != 0
                                && !schemeModel.getImo().contains(ship.getImo())) {
                            continue;
                        }
                        if(checkShipTimeWindows(ship,schemeDetail)){
                            loadShipInfo = ship;
                            break;
                        }
                    }
                }
            }else{
                //优先小船
                if(minShips.size() > 0){
                    for (SpInformation ship : minShips) {
                        if (schemeModel.getImo() != null && schemeModel.getImo().size() != 0
                                && !schemeModel.getImo().contains(ship.getImo())) {
                            continue;
                        }
                        if(checkShipTimeWindows(ship,schemeDetail)){
                            loadShipInfo = ship;
                            break;
                        }
                    }
                }
                if(loadShipInfo == null && maxShips.size() > 0){
                    //小船都不符合，则派大船
                    for (SpInformation ship : maxShips) {
                        if (schemeModel.getImo() != null && schemeModel.getImo().size() != 0
                                && !schemeModel.getImo().contains(ship.getImo())) {
                            continue;
                        }
                        if(checkShipTimeWindows(ship,schemeDetail)){
                            loadShipInfo = ship;
                            break;
                        }
                    }
                }
            }

            if(loadShipInfo == null){
                //没有可用的船，走虚拟船逻辑
                if(!useVirtualShip && virtualShips.size() > 0){
                    insertData = virtualShipDispatch(virtualShips,schemeModel
                            ,goodsQuantity,spDispatchScheme.getPlanId(), year
                            , resourceMemorandum, spDispatchScheme.getId(),loadDays);
                }else{
                    List<SpInformation> ships = new LinkedList<>();
                    SpInformation virtualShip = spInformationService.createVirtualSpInformation();
                    ships.add(virtualShip);
                    insertData = virtualShipDispatch(ships,schemeModel
                            ,goodsQuantity,spDispatchScheme.getPlanId(), year
                            , resourceMemorandum, spDispatchScheme.getId(),loadDays);
                }
            }else{
                // 本艘船能够装载的货量（百万英热），排货默认满载
                BigDecimal mut = loadShipInfo.getStorageCapacity().multiply(BigDecimal.valueOf(0.985))
                        .subtract(BigDecimal.valueOf(500)).multiply(BigDecimal.valueOf(22.5))
                        .setScale(0, BigDecimal.ROUND_DOWN);

                if (goodsQuantity != null) {
                    if(allGoodsQuantity.compareTo(mut) > 0){
                        if(goodsQuantity.compareTo(mut) < 0){
                            //如果剩余货量达不到当前船只满载，则舍去这船货
                            goodsQuantity = BigDecimal.ZERO;
                            break;
                        }
                    }else{
                        //输入货量小于舱容，则说明只拉一船货，装载量=货量
                        mut = goodsQuantity;
                    }
                    goodsQuantity = goodsQuantity.subtract(mut);
                }
                schemeDetail.setStorageCapacity(loadShipInfo.getStorageCapacity());
                schemeDetail.setShipName(loadShipInfo.getNameOfVessel());
                schemeDetail.setImo(loadShipInfo.getImo());
                schemeDetail.setSingleGoodsQuantity(mut);
                schemeDetail.setRentTimeSlot(loadShipInfo.getRentTimeSlot());
                schemeDetail.setOilConsumption(loadShipInfo.getOilConsumption());

                getDispatchIntervalDaysAndTradeRate(schemeDetail,loadShipInfo,resourceMemorandum);

                insertData.add(schemeDetail);
            }
            if(insertData.size() == 0){
                return JsonResult.failed("当前方案无符合的船只或无可用时间空挡！");
            }

            for (SpDispatchSchemeDetail insertDetail : insertData) {
                this.baseMapper.insert(insertDetail);
                updateShipSchedule(insertDetail);
            }

            //获取本次排货最开始的装载时间
            SpDispatchSchemeDetail detailFirst = insertData.stream().min(Comparator.comparing((SpDispatchSchemeDetail::getExpectedLoadingDate))).get();
            Date resetStartDate = detailFirst.getExpectedLoadingDate();

            //重新生成航次号和货号
            List<SpDispatchSchemeDetail> detailList = this.baseMapper.selectList(
                    new LambdaQueryWrapper<SpDispatchSchemeDetail>().eq(SpDispatchSchemeDetail::getSchemeId, spDispatchScheme.getId()));
            if (detailList.size() > 0) {
                detailList = CommonUtil.resetVoyageNoAndGoodsNumberDispatch(detailList);

                detailList = resetSetLastEmptyInfo(detailList,resetStartDate);

                //重新计算一下前后空挡信息
                detailList = getIntervalDays(detailList,null);

                //计算优化成本及船舶利用率
                detailList = getUseRateAndDispatchCost(detailList,null);

                this.updateBatchById(detailList);
            }
            //调度求解器
//            schemeOptimize(detailList);
        }
        return JsonResult.success(tipString);
    }

    /**
     * 获取船货前后空挡天数
     * @param detailAllList
     * @return
     */
    public List<SpDispatchSchemeDetail> getUseRateAndDispatchCost(List<SpDispatchSchemeDetail> detailAllList, Integer year){
        if(year == null){
            LocalDate currentDate = LocalDate.now();
            year = currentDate.getYear();
        }

        List<SpInformation> spInformations = spInformationService.getAllSpInformationUsed(null);
        if(spInformations.size() == 0){
            return detailAllList;
        }
        if(detailAllList.size() == 0){
            return detailAllList;
        }
        Map<String,List<SpInformation>> shipMap = spInformations.stream().collect(Collectors.groupingBy(SpInformation::getImo));
        Map<String,List<SpDispatchSchemeDetail>> schemeDetailMap = detailAllList.stream().collect(Collectors.groupingBy(SpDispatchSchemeDetail::getImo));

        List<SpDispatchSchemeDetail> resultList = new LinkedList<>();
        for (String s : schemeDetailMap.keySet()) {
            List<SpDispatchSchemeDetail> detailList = schemeDetailMap.get(s);
            if(shipMap.get(s) == null){
                resultList.addAll(detailList);
                continue;
            }
            SpInformation spInformation = shipMap.get(s).get(0);
            BigDecimal yearInRentDays = BigDecimal.ZERO;
            BigDecimal yearInRentMoney = BigDecimal.ZERO;
            BigDecimal yearOutRentDays = BigDecimal.ZERO;
            BigDecimal yearOutRentMoney = BigDecimal.ZERO;

            ShipRentYearInfoBO yearInfoBO = CommonUtil.getShipYearTradeInfo(spInformation,year);
            yearInRentDays = yearInRentDays.add(yearInfoBO.getYearInRentDays());
            yearInRentMoney = yearInRentMoney.add(yearInfoBO.getYearInRentMoney());
            yearOutRentDays = yearOutRentDays.add(yearInfoBO.getYearOutRentDays());
            yearOutRentMoney = yearOutRentMoney.add(yearInfoBO.getYearOutRentMoney());

            BigDecimal shipDays = BigDecimal.ZERO;
            for (SpDispatchSchemeDetail schemeDetail : detailList) {
                if(schemeDetail.getPlanYear() != null && schemeDetail.getPlanYear().intValue() != year){
                    continue;
                }
                BigDecimal currentDay = BigDecimal.ZERO;
                if(schemeDetail.getExpectedFullLoadVoyageDays() != null){
                    currentDay = currentDay.add(new BigDecimal(String.valueOf(schemeDetail.getExpectedFullLoadVoyageDays())));
                }
                if(schemeDetail.getExpectedEmptyLoadVoyageDays() != null){
                    currentDay = currentDay.add(new BigDecimal(String.valueOf(schemeDetail.getExpectedEmptyLoadVoyageDays())));
                }
                shipDays = shipDays.add(currentDay);

                if(yearInRentDays.compareTo(BigDecimal.ZERO) != 0) {
                    schemeDetail.setShipUseRate(currentDay.divide(yearInRentDays, 3, RoundingMode.HALF_UP).multiply(new BigDecimal("100")));
                }
            }

            //计算船舶优化成本=外租收益-运力成本
            //外租收益=所有租出船舶租金*该船舶本年度合同租出天数之和
            //运力成本=所有船舶的租金*年度租的天数-租出的船舶天数*所有船舶租出的租金
            BigDecimal dispatchCost = yearOutRentMoney.subtract(yearInRentMoney.subtract(yearOutRentMoney));
            dispatchCost = dispatchCost.divide(new BigDecimal("10000"),0,RoundingMode.UP);
            for (SpDispatchSchemeDetail schemeDetail : detailList) {
                if(schemeDetail.getPlanYear() != null && schemeDetail.getPlanYear().intValue() != year){
                    continue;
                }
                schemeDetail.setDispatchCost(dispatchCost);
            }
            resultList.addAll(detailList);
        }
        return resultList;
    }

    /**
     * 重新计算上一航次信息
     * @param detailList
     * @return
     */
    public List<SpDispatchSchemeDetail> resetSetLastEmptyInfo(List<SpDispatchSchemeDetail> detailList, Date resetStartDate){
        for (SpDispatchSchemeDetail schemeDetail : detailList) {
            //只需要对第一个优化时间之后的货物进行空挡重新计算
            if(resetStartDate != null && schemeDetail.getExpectedLoadingDate().before(resetStartDate)){
                continue;
            }

            //根据船只上一航次卸载港-当前航次装载港计算空载时间
            //预计空载航程天数
            SpDispatchSchemeDetail lastSchemeDetail = this.baseMapper.selectOne(
                    new LambdaQueryWrapper<SpDispatchSchemeDetail>()
                            .lt(SpDispatchSchemeDetail::getExpectedLoadingDate,schemeDetail.getExpectedLoadingDate())
                            .eq(SpDispatchSchemeDetail::getImo,schemeDetail.getImo())
                            .eq(SpDispatchSchemeDetail::getSchemeId,schemeDetail.getSchemeId())
                            .orderByDesc(SpDispatchSchemeDetail::getExpectedLoadingDate)
                            .last("limit 1")
            );
            schemeDetail.setLastShipSpeed(schemeDetail.getShipSpeed());
            if(lastSchemeDetail == null){
                //没有上一船货，则没有空载时间
                schemeDetail.setExpectedEmptyLoadVoyageDays(0);
                schemeDetail.setExpectedEmptyReturnWindow(schemeDetail.getExpectedLoadingDate());

                //没有空载时间则清空空载航线信息
                schemeDetail.setEmptyDistance(0);
                schemeDetail.setLastUnloadPortName("");
            }else{
                ShipRouteSearchVo routeDay = spPortRouteDistanceService.getShipRouteDayByThirdId(schemeDetail.getShipSpeed(),lastSchemeDetail.getExpectedUnloadThirdId()
                        ,schemeDetail.getExpectedLoadThirdId(),null,null);
                if(routeDay != null){
                    int day = routeDay.getShipRouteDay().intValue();
                    schemeDetail.setEmptyDistance(routeDay.getTrackDistance());
                    schemeDetail.setLastUnloadPortName(lastSchemeDetail.getExpectedUnloadingPort());
                    schemeDetail.setExpectedEmptyLoadVoyageDays(day);
                    schemeDetail.setExpectedEmptyReturnWindow(DateUtil.offsetDay(schemeDetail.getExpectedLoadingDate(), ~day + 1));
                }else{
                    schemeDetail.setExpectedEmptyLoadVoyageDays(0);
                    schemeDetail.setExpectedEmptyReturnWindow(schemeDetail.getExpectedLoadingDate());
                }
            }
        }
        return detailList;
    }

    /**
     * 虚拟船排货逻辑
     * @param virtualShips
     * @param schemeModel
     * @param goodsQuantity
     * @param planId
     * @param year
     * @param resource
     * @param schemeId
     */
    public List<SpDispatchSchemeDetail> virtualShipDispatch(List<SpInformation> virtualShips,SpDispatchSchemeDetailModel schemeModel
            ,BigDecimal goodsQuantity,Long planId, int year, ConResourceMemorandum resource
            , Long schemeId,ShipLoadDaysAndRouteVo loadDays){
        List<SpDispatchSchemeDetail> insertData = new LinkedList<>();
        BigDecimal virGoodsQuantity = goodsQuantity;
        Date loadDate = schemeModel.getExpectedLoadingDate();
        if (virGoodsQuantity != null) {
            if(virGoodsQuantity.compareTo(BigDecimal.ZERO) <= 0){
                return insertData;
            }
        }

        //根据装载时间初步生成船货信息
        SpDispatchSchemeDetail schemeDetail = initDispatchSchemeDetail(year,resource,schemeId,loadDate,schemeModel,loadDays);
        schemeDetail.setPlanId(planId);

        SpInformation virtualShip = null;
        for (SpInformation spInformation : virtualShips) {
            if(checkShipTimeWindows(spInformation,schemeDetail)){
                virtualShip = spInformation;
                break;
            }
        }
        if(virtualShip == null){
            return insertData;
        }

        // 本艘船能够装载的货量（百万英热），排货默认满载
        BigDecimal mut = virtualShip.getStorageCapacity().multiply(BigDecimal.valueOf(0.985))
                .subtract(BigDecimal.valueOf(500)).multiply(BigDecimal.valueOf(22.5))
                .setScale(0, BigDecimal.ROUND_DOWN);
        schemeDetail.setShipName(virtualShip.getNameOfVessel());
        schemeDetail.setImo(virtualShip.getImo());
        schemeDetail.setSingleGoodsQuantity(mut);
        schemeDetail.setOilConsumption(virtualShip.getOilConsumption());

        getDispatchIntervalDaysAndTradeRate(schemeDetail,virtualShip,null);
        schemeDetail.setStorageCapacity(virtualShip.getStorageCapacity());
        insertData.add(schemeDetail);
        return insertData;
    }

    /**
     * 更新船期表
     * @param schemeDetail
     */
    public void updateShipSchedule(SpDispatchSchemeDetail schemeDetail){
        if(schemeDetail.getImo().indexOf("virtualShip") > -1){
            SpInformation spInformation = spInformationService.getBaseMapper().selectOne(
                    new LambdaQueryWrapper<SpInformation>()
                    .eq(SpInformation::getImo,schemeDetail.getImo())
                    .last("limit 1")
            );
            if(spInformation != null && StringUtils.isBlank(spInformation.getRentTimeSlot())){
                //虚拟船租金信息为空，说明是刚新增的，需要根据装卸载时间来进行租期设置
                Date startDate = schemeDetail.getExpectedLoadingDate();
                Date endDate = schemeDetail.getExpectedUnloadingWindow();

                Calendar calendar1 = Calendar.getInstance();
                calendar1.setTime(startDate);
                Calendar calendar2 = Calendar.getInstance();
                calendar2.setTime(endDate);
                if(calendar1.get(Calendar.YEAR) != calendar2.get(Calendar.YEAR)){
                    //说明跨年了，租期结束时间得推到卸载时间得年末
                    calendar2.set(Calendar.MONTH, Calendar.DECEMBER);
                    calendar2.set(Calendar.DAY_OF_MONTH, 31);
                    endDate = calendar2.getTime();
                }

                //组装租期、租金信息
                JSONArray array = new JSONArray();
                JSONObject json = new JSONObject();
                json.put("rentDateBegin",startDate);
                json.put("rentDateEnd",endDate);
                json.put("rentParagraph",6000);
                json.put("rentUnit",1);
                array.add(json);
                spInformation.setRentTimeSlot(array.toJSONString());
                spInformationService.getBaseMapper().updateById(spInformation);
            }
        }

        //使用这个变量来判断这艘船是否再租出时间范围内，如果再就为true
        AtomicReference<Boolean> judge = new AtomicReference<>(false);
        //起租开始时间
        AtomicReference<Date> startingDateOfLeaseBegin = new AtomicReference<>();
        //还船结束时间
        AtomicReference<Date> redeliveryDateEnd = new AtomicReference<>();
        //租出时间，多条
        List<String> leaseTermStrs = new ArrayList<>();
        //从船运合同中获取信息
        List<ConShipping> conShippings = conShippingMapper.getByIMO(schemeDetail.getImo());
        if (conShippings != null && conShippings.size() > 0) {
            conShippings.stream().forEach(c -> {
                if (c.getContractType() != null && c.getContractType().equals(3)) {
                    //租出
                    leaseTermStrs.add(DateUtil.format(c.getDeliveryDateBegin(), "yyyy-MM-dd HH:mm:ss") + "," + DateUtil.format(c.getDeliveryDateEnd(), "yyyy-MM-dd HH:mm:ss"));
                    //空载时间小于交船时间
                    if (c.getDeliveryDateBegin()!=null && !schemeDetail.getExpectedEmptyReturnWindow().before(c.getDeliveryDateBegin())) {
                        judge.set(true);
                        //装载时间大于还船时间
                        if (c.getDeliveryDateEnd()!=null && !schemeDetail.getExpectedLoadingDate().after(c.getDeliveryDateEnd())) {
                            judge.set(true);
                        }else{
                            judge.set(false);
                        }
                    }
                } else {
                    schemeDetail.setShipContractId(c.getId());
                    schemeDetail.setShipContractName(c.getContractName());
                    schemeDetail.setShipTradeBody(c.getTradeBody());
                    schemeDetail.setShipContractType(c.getContractType());
                    if (c.getDeliveryDateBegin() != null) {
                        startingDateOfLeaseBegin.set(c.getDeliveryDateBegin());
                    }
                    redeliveryDateEnd.set(c.getRedeliveryDateEnd());
                }
                //off-hire状态，时间段不能生成货数.并且记录在船期表中
                if(c.getStatus()!=null && c.getStatus().equals(5)){
                    if(StringUtils.isNotBlank(c.getHireTimeValue())){
                        List<HireTimeDetailsBO> hireTimeDetailsBOS = new ArrayList<>();
                        ObjectMapper objectMapper = new ObjectMapper();
                        try {
                            hireTimeDetailsBOS = objectMapper.readValue(c.getHireTimeValue(), new TypeReference<List<HireTimeDetailsBO>>() {
                            });
                            if(hireTimeDetailsBOS!=null && hireTimeDetailsBOS.size()>0){
                                hireTimeDetailsBOS.stream().forEach(h->{
                                    //空载时间小于交船时间
                                    if (h.getStart()!=null && !schemeDetail.getExpectedEmptyReturnWindow().before(h.getStart())) {
                                        judge.set(true);
                                        //装载时间大于还船时间
                                        if (h.getEnd()!=null && !schemeDetail.getExpectedLoadingDate().after(h.getEnd())) {
                                            judge.set(true);
                                        }else{
                                            judge.set(false);
                                        }
                                    }
                                    leaseTermStrs.add(DateUtil.format(h.getStart(), "yyyy-MM-dd HH:mm:ss") + "," + DateUtil.format(h.getEnd(), "yyyy-MM-dd HH:mm:ss"));
                                });
                            }
                        } catch (JsonProcessingException e) {
                            throw new RuntimeException(e);
                        }
                    }
                }
            });
        }

        //保存船舶班期表
        SpShipSchedule spShipSchedule = new SpShipSchedule();
        BeanUtils.copyProperties(schemeDetail, spShipSchedule);
        spShipSchedule.setId(IDUtil.nextId());
        BigDecimal goodsQuantityNew = schemeDetail.getLoadingCapacity().multiply(BigDecimal.valueOf(0.985))
                .subtract(BigDecimal.valueOf(500)).multiply(BigDecimal.valueOf(22.5))
                .setScale(0, BigDecimal.ROUND_DOWN);
        //生成年度计划，每船货上下浮动3.25%
        BigDecimal storageCapacityFloat = goodsQuantityNew.multiply(BigDecimal.valueOf(0.0325))
                .setScale(0, BigDecimal.ROUND_DOWN);
        BigDecimal maxStorageCapacity = goodsQuantityNew.add(storageCapacityFloat);
        BigDecimal minStorageCapacity = goodsQuantityNew.subtract(storageCapacityFloat);
        spShipSchedule.setMaxStorageCapacity(maxStorageCapacity);
        spShipSchedule.setMinStorageCapacity(minStorageCapacity);
        spShipSchedule.setStorageCapacity(goodsQuantityNew);
        spShipSchedule.setExecuteStatus(0);
        if (startingDateOfLeaseBegin.get() != null) {
            spShipSchedule.setStartingDateOfLeaseBegin(startingDateOfLeaseBegin.get());
        }
        if (redeliveryDateEnd.get() != null) {
            spShipSchedule.setRedeliveryDateEnd(redeliveryDateEnd.get());
        }
        if (leaseTermStrs != null && leaseTermStrs.size() > 0) {
            spShipSchedule.setLeaseTermStr(leaseTermStrs.toString());
        }
        shipScheduleService.saveShipSchedule(spShipSchedule);
    }

    /**
     * 校验船只时间窗口是否符合
     * @param ship
     * @param schemeDetail
     * @return
     */
    public Boolean checkShipTimeWindows(SpInformation ship,SpDispatchSchemeDetail schemeDetail){
        Boolean isSuccess = false;

        //默认空载时间等于满载时间来计算
        schemeDetail.setExpectedEmptyReturnWindow(DateUtil.offsetDay(schemeDetail.getExpectedLoadingDate(), ~schemeDetail.getExpectedFullLoadVoyageDays() + 1));

        //当前船只已排货时间信息
        Long count = this.baseMapper.selectCount(
                new LambdaQueryWrapper<SpDispatchSchemeDetail>()
                        .eq(SpDispatchSchemeDetail::getSchemeId,schemeDetail.getSchemeId())
                        .eq(SpDispatchSchemeDetail::getImo,ship.getImo())
                        .ne(SpDispatchSchemeDetail::getIsFobSell,1)
                        .and(qw -> {
                            qw.between(SpDispatchSchemeDetail::getExpectedUnloadingWindow,schemeDetail.getExpectedEmptyReturnWindow(),schemeDetail.getExpectedUnloadingWindow());
                            qw.or().between(SpDispatchSchemeDetail::getExpectedEmptyReturnWindow,schemeDetail.getExpectedEmptyReturnWindow(),schemeDetail.getExpectedUnloadingWindow());
                        })
        );
        if(count > 0){
            return isSuccess;
        }

        //校验上一船货船期是否满足
        //预计空载航程天数
        SpDispatchSchemeDetail lastSchemeDetail = this.baseMapper.selectOne(
                new LambdaQueryWrapper<SpDispatchSchemeDetail>()
                        .eq(SpDispatchSchemeDetail::getSchemeId,schemeDetail.getSchemeId())
                        .lt(SpDispatchSchemeDetail::getExpectedLoadingDate,schemeDetail.getExpectedLoadingDate())
                        .eq(SpDispatchSchemeDetail::getImo,schemeDetail.getImo())
                        .orderByDesc(SpDispatchSchemeDetail::getExpectedLoadingDate)
                        .last("limit 1")
        );
        if(lastSchemeDetail != null){
            ShipRouteSearchVo routeDay = spPortRouteDistanceService.getShipRouteDayByThirdId(schemeDetail.getShipSpeed(),lastSchemeDetail.getExpectedUnloadThirdId()
                    ,schemeDetail.getExpectedLoadThirdId(),null,null);
            if(routeDay == null){
                //与上一船货卸载港无航线，则说明到不了，不能排货
                return isSuccess;
            }
            int day = routeDay.getShipRouteDay().intValue();

            Date currentEmptyDate = DateUtil.offsetDay(schemeDetail.getExpectedLoadingDate(), ~day - 1);

            if(currentEmptyDate.before(lastSchemeDetail.getExpectedUnloadingWindow())){
                return isSuccess;
            }
        }

        //校验当前船只下一船货船期是否满足
        SpDispatchSchemeDetail nextSchemeDetail = this.baseMapper.selectOne(
                new LambdaQueryWrapper<SpDispatchSchemeDetail>()
                        .eq(SpDispatchSchemeDetail::getSchemeId,schemeDetail.getSchemeId())
                        .gt(SpDispatchSchemeDetail::getExpectedLoadingDate,schemeDetail.getExpectedLoadingDate())
                        .eq(SpDispatchSchemeDetail::getImo,ship.getImo())
                        .orderByAsc(SpDispatchSchemeDetail::getExpectedLoadingDate)
                        .last("limit 1")
        );
        if(nextSchemeDetail != null){
            ShipRouteSearchVo routeDay = spPortRouteDistanceService.getShipRouteDayByThirdId(schemeDetail.getShipSpeed(),schemeDetail.getExpectedUnloadThirdId()
                    ,nextSchemeDetail.getExpectedLoadThirdId(),null,null);
            if(routeDay == null){
                return isSuccess;
            }
            int day = routeDay.getShipRouteDay().intValue();

            Date nextEmptyDate = DateUtil.offsetDay(nextSchemeDetail.getExpectedLoadingDate(), ~day - 1);

            if(nextEmptyDate.before(schemeDetail.getExpectedUnloadingWindow())){
                return isSuccess;
            }
        }

        if(ship.getIsVirtual().intValue() == 1){
            //虚拟船不需要校验船期
            return true;
        }

        //判断是否在出租期内
        List<ConShipping> rentShip = conShippingMapper.selectList(
                new LambdaQueryWrapper<ConShipping>()
                        .eq(ConShipping::getImo,ship.getImo())
                        .ne(ConShipping::getStatus,4)
                        .eq(ConShipping::getContractType,3)
        );
        for (ConShipping conShipping : rentShip) {
            //空载时间和装载时间在租期内，则表示已租出去，不能使用
            if ((schemeDetail.getExpectedEmptyReturnWindow().before(conShipping.getRedeliveryDateEnd())
                    && schemeDetail.getExpectedEmptyReturnWindow().after(conShipping.getDeliveryDateBegin()))
                    || (schemeDetail.getExpectedUnloadingWindow().after(conShipping.getDeliveryDateBegin())
                    && schemeDetail.getExpectedUnloadingWindow().before(conShipping.getRedeliveryDateEnd()))) {
                return isSuccess;
            }
        }

        //是否处于OFF-HIRE区间
        rentShip = conShippingMapper.selectList(
                new LambdaQueryWrapper<ConShipping>()
                        .eq(ConShipping::getImo,ship.getImo())
                        .eq(ConShipping::getStatus,5)
        );
        for (ConShipping conShipping : rentShip) {
            if(StringUtils.isBlank(conShipping.getHireTimeValue())){
                continue;
            }
            ObjectMapper objectMapper = new ObjectMapper();
            try {
                List<HireTimeDetailsBO> hireTimeDetailsBOS = objectMapper.readValue(conShipping.getHireTimeValue()
                        , new TypeReference<List<HireTimeDetailsBO>>() {});
                if(hireTimeDetailsBOS.size() == 0){
                    continue;
                }
                for (HireTimeDetailsBO hireTimeDetailsBO : hireTimeDetailsBOS) {
                    Boolean inStatus = (hireTimeDetailsBO.getStart().after(schemeDetail.getExpectedEmptyReturnWindow())
                            && hireTimeDetailsBO.getStart().before(schemeDetail.getExpectedUnloadingWindow()))
                            || (hireTimeDetailsBO.getEnd().after(schemeDetail.getExpectedEmptyReturnWindow())
                            && hireTimeDetailsBO.getEnd().before(schemeDetail.getExpectedUnloadingWindow()))
                            || (hireTimeDetailsBO.getEnd().after(schemeDetail.getExpectedUnloadingWindow())
                            && hireTimeDetailsBO.getStart().before(schemeDetail.getExpectedEmptyReturnWindow()));
                    if(inStatus){
                        return isSuccess;
                    }
                }
            } catch (JsonProcessingException e) {
                throw new RuntimeException(e);
            }
        }

        //是否在租入时间内
        rentShip = conShippingMapper.selectList(
                new LambdaQueryWrapper<ConShipping>()
                        .eq(ConShipping::getImo,ship.getImo())
                        .ne(ConShipping::getStatus,4)
                        .in(ConShipping::getContractType,1,2)
        );
        for (ConShipping conShipping : rentShip) {
            if (schemeDetail.getExpectedLoadingDate().before(conShipping.getRedeliveryDateEnd())
                    && schemeDetail.getExpectedLoadingDate().after(conShipping.getDeliveryDateBegin())) {
                schemeDetail.setShipContractId(conShipping.getId());
                schemeDetail.setShipContractName(conShipping.getContractName());
                schemeDetail.setShipTradeBody(conShipping.getTradeBody());
                schemeDetail.setShipContractType(conShipping.getContractType());
                return true;
            }
        }
        return false;
    }

    /**
     * 初始化方案详情信息
     * @param year
     * @param resource
     * @param schemeId
     * @param expectLoadDate
     * @param schemeModel
     * @return
     */
    public SpDispatchSchemeDetail initDispatchSchemeDetail(int year, ConResourceMemorandum resource
            , Long schemeId, Date expectLoadDate, SpDispatchSchemeDetailModel schemeModel,ShipLoadDaysAndRouteVo shipLoadDaysAndRouteVo){
        SpDispatchSchemeDetail schemeDetail = new SpDispatchSchemeDetail();
        //装载港
        schemeDetail.setExpectedLoadingPort(shipLoadDaysAndRouteVo.getStartPortName());
        schemeDetail.setExpectedLoadingPortId(shipLoadDaysAndRouteVo.getStartPortId());
        //卸载港
        schemeDetail.setExpectedUnloadingPort(shipLoadDaysAndRouteVo.getEndPortName());
        schemeDetail.setExpectedUnloadingPortId(shipLoadDaysAndRouteVo.getEndPortId());
        schemeDetail.setPassStrait(shipLoadDaysAndRouteVo.getPassStrait());
        schemeDetail.setPassArea(shipLoadDaysAndRouteVo.getPassArea());
        schemeDetail.setExpectedLoadThirdId(shipLoadDaysAndRouteVo.getStartThirdPortId());
        schemeDetail.setExpectedUnloadThirdId(shipLoadDaysAndRouteVo.getEndThirdPortId());
        schemeDetail.setShipSpeed(schemeModel.getSpeed());
        schemeDetail.setUnloadDistance(shipLoadDaysAndRouteVo.getTrackDistance());

        int shipDays = 0;
        int chinaDays = 0;
        int europeDays = 0;
        int indiaDays = 0;

        if (shipLoadDaysAndRouteVo.getShipDay() != null) {
            shipDays = shipLoadDaysAndRouteVo.getShipDay();
        }
        if (shipLoadDaysAndRouteVo.getChinaDays() != null) {
            chinaDays = shipLoadDaysAndRouteVo.getChinaDays();
        }
        if (shipLoadDaysAndRouteVo.getEuropeDays() != null) {
            europeDays = shipLoadDaysAndRouteVo.getEuropeDays();
        }
        if (shipLoadDaysAndRouteVo.getIndiaDays() != null) {
            indiaDays = shipLoadDaysAndRouteVo.getIndiaDays();
        }
        schemeDetail.setPlanYear(year);
        schemeDetail.setCreateBy(SecurityUtils.getUsername());
        schemeDetail.setId(IDUtil.nextId());
        schemeDetail.setSchemeId(schemeId);
        schemeDetail.setSchemeNumber(1);
        schemeDetail.setSchemeApprove(1);

        // 从资源合同服务获取资源方信息
        if (resource != null) {
            schemeDetail.setLoadingCapacity(resource.getLoadingOrUnloadingCapacity());
            schemeDetail.setLoadingCapacityUnit(resource.getUnit());
            schemeDetail.setResourceContract(schemeModel.getConId());
            if (resource.getContractName() != null) {
                schemeDetail.setContractName(resource.getContractName());
            }
            schemeDetail.setSeller(resource.getSeller());
            if (resource.getTradeType() != null) {
                schemeDetail.setTradeType(resource.getTradeType());
            }
            if (resource.getTradeBody() != null) {
                schemeDetail.setTradeBody(resource.getTradeBody());
            }
            schemeDetail.setLoadingCapacity(resource.getLoadingOrUnloadingCapacity());
            if (resource.getBuyer() != null) {
                schemeDetail.setBuyer(resource.getBuyer());
            }

            if(resource.getDeliveryType() != null && resource.getDeliveryType().intValue() == 1){
                if(resource.getTradeModel() != null && (resource.getTradeModel().intValue() == 2 || resource.getTradeModel().intValue() == 3)){
                    schemeDetail.setIsFobSell(1);
                }
            }
        }else{
            schemeDetail.setSeller("CNOOC");
        }

        if(schemeModel.getSellerConId() != null){
            //绑定销售合同
            ConResourceMemorandum resourceMemorandum = resourceMemorandumMapper.getById(schemeModel.getSellerConId());
            if(resourceMemorandum != null){
                schemeDetail.setSellPrice(resourceMemorandum.getContractPrice());
                schemeDetail.setSellerResourceContract(resourceMemorandum.getId());
                schemeDetail.setSellerContractName(resourceMemorandum.getContractName());
                schemeDetail.setSellerTradeType(resourceMemorandum.getTradeType());
                schemeDetail.setSellerTradeBody(resourceMemorandum.getTradeBody());
                if(resourceMemorandum.getDeliveryType() != null && resourceMemorandum.getDeliveryType().intValue() == 1){
                    if(resourceMemorandum.getTradeModel() != null && (resourceMemorandum.getTradeModel().intValue() == 2 || resourceMemorandum.getTradeModel().intValue() == 3)){
                        schemeDetail.setIsFobSell(1);
                    }
                }
            }
        }


        schemeDetail.setVoyageNo("CNOOC" + year + "" + "01");
        schemeDetail.setGoodsNum(schemeDetail.getSeller() + "FOB" + "01");
        //换港时间 1:合同中的换港时间约定，BP，窗口前 75 天，马石油中期，窗口所在月份第一天的前 60 天
        //换港时间 2:合同中的换港时间约定，BP，窗口前 60 天，马石油中期，窗口前 60 天
        if (resource != null && StringUtils.isNotBlank(resource.getChangePortTimeLoading())) {
            Date changePortTimeLoading = DateUtil.parse(resource.getChangePortTimeLoading(), "yyyy-MM-dd HH:mm:ss");
            if (schemeDetail.getSeller().equals("BP")) {
                schemeDetail.setPortChangeDateOne(DateUtil.offsetDay(changePortTimeLoading, -75));
                schemeDetail.setPortChangeDateTwo(DateUtil.offsetDay(changePortTimeLoading, -60));
            } else if (schemeDetail.getSeller().equals("马石油") && resource.getTradeType().equals(2)) {
                schemeDetail.setPortChangeDateOne(DateUtil.offsetDay(changePortTimeLoading, -60));
                schemeDetail.setPortChangeDateTwo(DateUtil.offsetDay(changePortTimeLoading, -60));
            }
        }

        //换船/指船时间 1:合同中的约定，BP，窗口前 45 天，马石油中期，窗口所在月份第一天的前 20 天
        //换船时间 2:合同中的约定，BP，窗口前 25 天，马石油中期，窗口前 20 天
        if (resource != null && StringUtils.isNotBlank(resource.getChangeShipTime())) {
            Date changeShipTime = DateUtil.parse(resource.getChangeShipTime(), "yyyy-MM-dd HH:mm:ss");
            if (schemeDetail.getSeller().equals("BP")) {
                schemeDetail.setShipChangeDateOne(DateUtil.offsetDay(changeShipTime, -45));
                schemeDetail.setShipChangeDateTwo(DateUtil.offsetDay(changeShipTime, -25));
            } else if (schemeDetail.getSeller().equals("马石油") && resource.getTradeType().equals(2)) {
                schemeDetail.setShipChangeDateOne(DateUtil.offsetDay(changeShipTime, -20));
                schemeDetail.setShipChangeDateTwo(DateUtil.offsetDay(changeShipTime, -20));
            }
        }

        schemeDetail.setExpectedLoadingDate(expectLoadDate);
        schemeDetail.setExpectedLoadingCompletionWindow(DateUtil.offsetDay(expectLoadDate, 2));

        //预计卸载时间
        if(schemeModel.getExpectedUnloadingWindow() == null){
            schemeDetail.setExpectedUnloadingWindow(DateUtil.offsetDay(expectLoadDate, shipDays));
            //预计满载航程天数
            schemeDetail.setExpectedFullLoadVoyageDays(shipDays);
        }else{
            schemeDetail.setExpectedUnloadingWindow(schemeModel.getExpectedUnloadingWindow());
            //预计满载航程天数
            String reaShipDays = String.valueOf(DateUtil.betweenDay(expectLoadDate,schemeModel.getExpectedUnloadingWindow(),true));
            schemeDetail.setExpectedFullLoadVoyageDays(Integer.parseInt(reaShipDays));
            BigDecimal shipSpeed = new BigDecimal(String.valueOf(schemeDetail.getUnloadDistance()))
                    .divide(new BigDecimal(reaShipDays),2,RoundingMode.HALF_UP)
                    .divide(new BigDecimal("24"),2,RoundingMode.HALF_UP);
            schemeDetail.setShipSpeed(shipSpeed);
        }

        //中国预计卸载时间
        schemeDetail.setChinaExpectedUnloadingWindow(DateUtil.offsetDay(expectLoadDate, chinaDays));
        //印度预计卸载时间
        schemeDetail.setIndiaUnloadTime(DateUtil.offsetDay(expectLoadDate, indiaDays));
        //欧洲预计卸载时间
        schemeDetail.setEuropeUnloadTime(DateUtil.offsetDay(expectLoadDate, europeDays));
        //印度空载航程天数
        schemeDetail.setIndiaEmptyLoadDays(indiaDays);
        //欧洲空载航程天数
        schemeDetail.setEuropeEmptyLoadDays(europeDays);
        //预计卸载完成窗口
        schemeDetail.setExpectedUnloadingCompletionWindow(DateUtil.offsetDay(schemeDetail.getExpectedUnloadingWindow(), 2));

        //预计空载返程时间
        schemeDetail.setExpectedEmptyReturnWindow(DateUtil.offsetDay(expectLoadDate, ~shipDays + 1));
        //中国预计空载返程窗口
        schemeDetail.setChinaExpectedEmptyReturnWindow(DateUtil.offsetDay(expectLoadDate, ~chinaDays + 1));
        //欧洲预计空载返程窗口
        schemeDetail.setEuropeExpectedEmptyReturnWindow(DateUtil.offsetDay(expectLoadDate, ~europeDays + 1));
        //印度预计空载返程窗口
        schemeDetail.setIndiaExpectedEmptyReturnWindow(DateUtil.offsetDay(expectLoadDate, ~indiaDays + 1));

        schemeDetail.setCreateDate(new Date());
        schemeDetail.setQuantityUnit(schemeModel.getQuantityUnit());
        //TODO:巴拿马运河预定时间:装载时间提前30天 写死，后面从字典表中获取
        schemeDetail.setPanamaCanalReservationDate(DateUtil.offsetDay(expectLoadDate, -30));
        return schemeDetail;
    }

    /**
     * 方案详情智能调度
     * @param originSchemeDetailList
     * @return
     */
    public List<SpDispatchSchemeDetail> schemeOptimize(List<SpDispatchSchemeDetail> originSchemeDetailList) {
        List<SpDispatchSchemeDetail> resultList = new LinkedList<>();

        JSONObject paramData = new JSONObject();
        paramData.put("request_id",String.valueOf(System.currentTimeMillis()));
        paramData.put("callback_url","www.xxxx.com/callback");

        JSONArray cargoArray = new JSONArray();
        JSONArray tankerArray = new JSONArray();
        //组装货运信息
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
        for (int i = 1; i <= originSchemeDetailList.size(); i++) {
            SpDispatchSchemeDetail spDispatchSchemeDetail = originSchemeDetailList.get(i-1);
            if(spDispatchSchemeDetail.getIsPotential() != null && spDispatchSchemeDetail.getIsPotential().intValue() == 2){
                //潜在投标不需要参与智能调度
                continue;
            }
            String cargoCode = "cargo_code_"+i;
            spDispatchSchemeDetail.setCargoCode(cargoCode);
            JSONObject cargoJson = new JSONObject();
            cargoJson.put("cargo_code",cargoCode);
            cargoJson.put("on_port",spDispatchSchemeDetail.getExpectedLoadingPort());
            cargoJson.put("on_date",dateFormat.format(spDispatchSchemeDetail.getExpectedLoadingDate()));
            cargoJson.put("on_payload",spDispatchSchemeDetail.getSingleGoodsQuantity().toString());
            cargoJson.put("off_port",spDispatchSchemeDetail.getExpectedLoadingPort());
            cargoJson.put("off_date",dateFormat.format(spDispatchSchemeDetail.getExpectedUnloadingWindow()));
            cargoJson.put("off_payload",spDispatchSchemeDetail.getSingleGoodsQuantity().toString());
            cargoJson.put("knot",spDispatchSchemeDetail.getShipSpeed().toString());
            List<String> routeList = new LinkedList<>();
            if(StringUtils.isNotBlank(spDispatchSchemeDetail.getShipRoute())){
                String route = spDispatchSchemeDetail.getShipRoute();
                if(route.indexOf("1") > -1){
                    routeList.add("巴拿⻢运河");
                }
                if(route.indexOf("2") > -1){
                    routeList.add("苏伊⼠运河");
                }
                if(route.indexOf("3") > -1){
                    routeList.add("好望⻆海峡");
                }
            }
            cargoJson.put("route",routeList.stream().collect(Collectors.joining("|")));
            cargoJson.put("location","eastern");
            cargoJson.put("trade_subject",String.valueOf(spDispatchSchemeDetail.getTradeBody()));
            cargoArray.add(cargoJson);

            SpInformation spInformation = spInformationService.getBaseMapper().selectOne(
                    new LambdaQueryWrapper<SpInformation>()
                    .eq(SpInformation::getImo,spDispatchSchemeDetail.getImo())
                    .last("limit 1")
            );
            if(spInformation == null){
                continue;
            }

            //根据imo查询船只上次卸载时间和下次装载时间
            Map<String,Date> timeMap = this.baseMapper.getLastUnTimeAndNextTime(spDispatchSchemeDetail.getImo(),spDispatchSchemeDetail.getExpectedLoadingDate());

            JSONObject tankerJson = new JSONObject();
            tankerJson.put("tanker_id",spDispatchSchemeDetail.getImo());
            tankerJson.put("load",spDispatchSchemeDetail.getLoadingCapacity().setScale(0, RoundingMode.HALF_UP).toString());
            tankerJson.put("gpm",spDispatchSchemeDetail.getOilConsumption().toString());
            tankerJson.put("country",spInformation.getCountry());
            if(timeMap != null && timeMap.get("expected_unloading_window") != null){
                tankerJson.put("pre_off_date",dateFormat.format(timeMap.get("expected_unloading_window")));
            }
            if(timeMap != null && timeMap.get("expected_loading_date") != null){
                tankerJson.put("next_on_date",dateFormat.format(timeMap.get("expected_loading_date")));
            }
            tankerJson.put("trade_subject",String.valueOf(spDispatchSchemeDetail.getTradeBody()));
            tankerJson.put("location","eastern");
            tankerArray.add(tankerJson);
        }
        paramData.put("cargo",cargoArray);
        paramData.put("tankers",tankerArray);
        System.out.println("调度求解器发送入参："+paramData.toJSONString());
        RequestBody body = RequestBody.create(paramData.toJSONString(),MediaType.parse("application/json"));
        Object resultObj = OkHttpUtil.sendOkHttpClient(dispatchSolverUrl, "POST",body,null);
        System.out.println("调度求解器发送出参："+resultObj);
        if(resultObj == null){
            return resultList;
        }
        JSONObject resultData = JSONObject.parseObject(resultObj.toString());
        if(resultData.getIntValue("code") != 0){
            return resultList;
        }
        JSONArray dataArray = resultData.getJSONObject("data").getJSONArray("tankers");
        Map<String,List<JSONObject>> smartDataMap = new HashMap<>();
        for (Object o : dataArray) {
            JSONObject jsonObject = JSONObject.parseObject(o.toString());
            if(smartDataMap.get(jsonObject.get("cargo_code")) != null){
                smartDataMap.get(jsonObject.get("cargo_code")).add(jsonObject);
                continue;
            }
            List<JSONObject> jsonObjects = new LinkedList<>();
            jsonObjects.add(jsonObject);
            smartDataMap.put(jsonObject.getString("cargo_code"),jsonObjects);
        }
        for (SpDispatchSchemeDetail spDispatchSchemeDetail : originSchemeDetailList) {
            if(spDispatchSchemeDetail.getCargoCode() == null){
                //潜在投标不需要参与智能调度
                resultList.add(spDispatchSchemeDetail);
                continue;
            }
            if(smartDataMap.get(spDispatchSchemeDetail.getCargoCode()) == null){
                continue;
            }
            List<JSONObject> jsonObjectList = smartDataMap.get(spDispatchSchemeDetail.getCargoCode());
            Boolean isAdd = false;
            for (JSONObject jsonObject : jsonObjectList) {
                if(jsonObject.getString("tanker_id").equals(spDispatchSchemeDetail.getImo())){
                    isAdd = true;
                }
            }
            if (isAdd) {
                resultList.add(spDispatchSchemeDetail);
            }
        }
        System.out.println("调度求解器最终输出结果："+resultObj);
        return resultList;
    }

    @Override
    public JsonResult capacityUpdate(SpYearPlanCapacityRecord dto) {
        if(dto.getRecordId() == null || dto.getPlanGoodsCapacity() == null){
            return JsonResult.failed("数据ID或计划量不能为空！");
        }
        SpYearPlanCapacityRecord capacityRecord = spYearPlanCapacityRecordService.getById(dto.getRecordId());
        if(capacityRecord == null){
            return JsonResult.failed("记录不存在！");
        }
        capacityRecord.setPlanGoodsCapacity(dto.getPlanGoodsCapacity());
        capacityRecord.setBalanceGoodsCapacity(capacityRecord.getPlanGoodsCapacity().subtract(capacityRecord.getRealGoodsCapacity()));
        spYearPlanCapacityRecordService.updateById(capacityRecord);
        return JsonResult.success();
    }
}
